Compare commits
No commits in common. "84fe2eee98c3e5c298dc51b7ff5a0536f1e0b3c6" and "679a1e0776774e2b13c94d2c1bc09ae6601162d6" have entirely different histories.
84fe2eee98
...
679a1e0776
2
go.mod
2
go.mod
@ -4,7 +4,6 @@ go 1.24.0
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
fyne.io/fyne/v2 v2.6.3
|
fyne.io/fyne/v2 v2.6.3
|
||||||
github.com/godbus/dbus/v5 v5.1.0
|
|
||||||
github.com/liyue201/goqr v0.0.0-20200803022322-df443203d4ea
|
github.com/liyue201/goqr v0.0.0-20200803022322-df443203d4ea
|
||||||
github.com/makiuchi-d/gozxing v0.1.0
|
github.com/makiuchi-d/gozxing v0.1.0
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||||
@ -26,6 +25,7 @@ require (
|
|||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect
|
||||||
github.com/go-text/render v0.2.0 // indirect
|
github.com/go-text/render v0.2.0 // indirect
|
||||||
github.com/go-text/typesetting v0.2.1 // indirect
|
github.com/go-text/typesetting v0.2.1 // indirect
|
||||||
|
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||||
github.com/hack-pad/go-indexeddb v0.3.2 // indirect
|
github.com/hack-pad/go-indexeddb v0.3.2 // indirect
|
||||||
github.com/hack-pad/safejs v0.1.0 // indirect
|
github.com/hack-pad/safejs v0.1.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
|||||||
@ -1,259 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/godbus/dbus/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
portalBusName = "org.freedesktop.portal.Desktop"
|
|
||||||
portalObjectPath = dbus.ObjectPath("/org/freedesktop/portal/desktop")
|
|
||||||
screenCastInterface = "org.freedesktop.portal.ScreenCast"
|
|
||||||
requestInterface = "org.freedesktop.portal.Request"
|
|
||||||
sessionInterface = "org.freedesktop.portal.Session"
|
|
||||||
)
|
|
||||||
|
|
||||||
type portalStream struct {
|
|
||||||
NodeID uint32
|
|
||||||
Properties map[string]dbus.Variant
|
|
||||||
}
|
|
||||||
|
|
||||||
func openPortalScreenCastSession() (*screenCaptureSession, error) {
|
|
||||||
if _, err := exec.LookPath("gst-launch-1.0"); err != nil {
|
|
||||||
return nil, errors.New("chybí gst-launch-1.0 pro přenos GNOME ScreenCast")
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := dbus.ConnectSessionBus()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("připojení k desktop portálu: %w", err)
|
|
||||||
}
|
|
||||||
closeConnection := true
|
|
||||||
defer func() {
|
|
||||||
if closeConnection {
|
|
||||||
conn.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
portal := conn.Object(portalBusName, portalObjectPath)
|
|
||||||
token := fmt.Sprintf("fckeuspy%d", time.Now().UnixNano())
|
|
||||||
createResult, err := portalRequest(conn, portal, screenCastInterface+".CreateSession", map[string]dbus.Variant{
|
|
||||||
"handle_token": dbus.MakeVariant(token + "create"),
|
|
||||||
"session_handle_token": dbus.MakeVariant(token + "session"),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("vytvoření ScreenCast relace: %w", err)
|
|
||||||
}
|
|
||||||
sessionPath, err := portalObjectPathResult(createResult, "session_handle")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
sessionOpen := true
|
|
||||||
defer func() {
|
|
||||||
if sessionOpen {
|
|
||||||
_ = portal.Call(sessionInterface+".Close", 0, sessionPath).Err
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if _, err := portalRequest(conn, portal, screenCastInterface+".SelectSources", sessionPath, map[string]dbus.Variant{
|
|
||||||
"handle_token": dbus.MakeVariant(token + "select"),
|
|
||||||
"types": dbus.MakeVariant(uint32(3)), // monitor or window
|
|
||||||
"multiple": dbus.MakeVariant(false),
|
|
||||||
"cursor_mode": dbus.MakeVariant(uint32(1)), // hidden
|
|
||||||
}); err != nil {
|
|
||||||
return nil, fmt.Errorf("výběr okna nebo monitoru: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
startResult, err := portalRequest(conn, portal, screenCastInterface+".Start", sessionPath, "", map[string]dbus.Variant{
|
|
||||||
"handle_token": dbus.MakeVariant(token + "start"),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("spuštění ScreenCast relace: %w", err)
|
|
||||||
}
|
|
||||||
streams, err := portalStreams(startResult)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(streams) != 1 {
|
|
||||||
return nil, fmt.Errorf("ScreenCast vrátil %d streamů, očekáván je jeden", len(streams))
|
|
||||||
}
|
|
||||||
|
|
||||||
var remote dbus.UnixFD
|
|
||||||
if err := portal.Call(screenCastInterface+".OpenPipeWireRemote", 0, sessionPath, map[string]dbus.Variant{}).Store(&remote); err != nil {
|
|
||||||
return nil, fmt.Errorf("otevření PipeWire streamu: %w", err)
|
|
||||||
}
|
|
||||||
frames, err := openPipeWireFrameStream(remote, streams[0].NodeID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
closeConnection = false
|
|
||||||
sessionOpen = false
|
|
||||||
return &screenCaptureSession{
|
|
||||||
capture: frames.Capture,
|
|
||||||
close: func() {
|
|
||||||
frames.Close()
|
|
||||||
_ = portal.Call(sessionInterface+".Close", 0, sessionPath).Err
|
|
||||||
conn.Close()
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func portalRequest(conn *dbus.Conn, portal dbus.BusObject, method string, args ...any) (map[string]dbus.Variant, error) {
|
|
||||||
signals := make(chan *dbus.Signal, 1)
|
|
||||||
conn.Signal(signals)
|
|
||||||
defer conn.RemoveSignal(signals)
|
|
||||||
if err := conn.AddMatchSignal(
|
|
||||||
dbus.WithMatchInterface(requestInterface),
|
|
||||||
dbus.WithMatchMember("Response"),
|
|
||||||
); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer conn.RemoveMatchSignal(
|
|
||||||
dbus.WithMatchInterface(requestInterface),
|
|
||||||
dbus.WithMatchMember("Response"),
|
|
||||||
)
|
|
||||||
|
|
||||||
var requestPath dbus.ObjectPath
|
|
||||||
if err := portal.Call(method, 0, args...).Store(&requestPath); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout := time.NewTimer(2 * time.Minute)
|
|
||||||
defer timeout.Stop()
|
|
||||||
select {
|
|
||||||
case <-timeout.C:
|
|
||||||
return nil, errors.New("desktop portál neodpověděl včas")
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case signal := <-signals:
|
|
||||||
if signal.Path != requestPath {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(signal.Body) != 2 {
|
|
||||||
return nil, errors.New("neplatná odpověď desktop portálu")
|
|
||||||
}
|
|
||||||
response, ok := signal.Body[0].(uint32)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("neplatný stav odpovědi desktop portálu")
|
|
||||||
}
|
|
||||||
if response != 0 {
|
|
||||||
return nil, errors.New("výběr byl zrušen")
|
|
||||||
}
|
|
||||||
result, ok := signal.Body[1].(map[string]dbus.Variant)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("neplatná data odpovědi desktop portálu")
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
case <-timeout.C:
|
|
||||||
return nil, errors.New("desktop portál neodpověděl včas")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func portalObjectPathResult(result map[string]dbus.Variant, key string) (dbus.ObjectPath, error) {
|
|
||||||
value, ok := result[key]
|
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("desktop portál nevrátil %s", key)
|
|
||||||
}
|
|
||||||
var path dbus.ObjectPath
|
|
||||||
if err := value.Store(&path); err != nil {
|
|
||||||
return "", fmt.Errorf("neplatné %s: %w", key, err)
|
|
||||||
}
|
|
||||||
return path, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func portalStreams(result map[string]dbus.Variant) ([]portalStream, error) {
|
|
||||||
value, ok := result["streams"]
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("desktop portál nevrátil žádný stream")
|
|
||||||
}
|
|
||||||
var streams []portalStream
|
|
||||||
if err := value.Store(&streams); err != nil {
|
|
||||||
return nil, fmt.Errorf("neplatná data streamu desktop portálu: %w", err)
|
|
||||||
}
|
|
||||||
return streams, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type pipeWireFrameStream struct {
|
|
||||||
dir string
|
|
||||||
command *exec.Cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func openPipeWireFrameStream(remote dbus.UnixFD, nodeID uint32) (*pipeWireFrameStream, error) {
|
|
||||||
dir, err := os.MkdirTemp("", "fckeuspy-pipewire-*")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
remoteFile := os.NewFile(uintptr(remote), "pipewire-remote")
|
|
||||||
command := exec.Command(
|
|
||||||
"gst-launch-1.0", "-q",
|
|
||||||
"pipewiresrc", "fd=3", fmt.Sprintf("path=%d", nodeID),
|
|
||||||
"!", "videoconvert",
|
|
||||||
"!", "videorate",
|
|
||||||
"!", "video/x-raw,framerate=5/1",
|
|
||||||
"!", "pngenc", "compression-level=1",
|
|
||||||
"!", "multifilesink", "location="+filepath.Join(dir, "frame-%05d.png"), "max-files=2",
|
|
||||||
)
|
|
||||||
command.ExtraFiles = []*os.File{remoteFile}
|
|
||||||
if err := command.Start(); err != nil {
|
|
||||||
remoteFile.Close()
|
|
||||||
os.Remove(dir)
|
|
||||||
return nil, fmt.Errorf("spuštění PipeWire čtečky: %w", err)
|
|
||||||
}
|
|
||||||
remoteFile.Close()
|
|
||||||
return &pipeWireFrameStream{dir: dir, command: command}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *pipeWireFrameStream) Capture() (image.Image, error) {
|
|
||||||
entries, err := os.ReadDir(s.dir)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(entries) == 0 {
|
|
||||||
return nil, errors.New("PipeWire zatím neposkytl žádný snímek")
|
|
||||||
}
|
|
||||||
sort.Slice(entries, func(i, j int) bool {
|
|
||||||
left, leftErr := entries[i].Info()
|
|
||||||
right, rightErr := entries[j].Info()
|
|
||||||
if leftErr != nil || rightErr != nil {
|
|
||||||
return entries[i].Name() > entries[j].Name()
|
|
||||||
}
|
|
||||||
return left.ModTime().After(right.ModTime())
|
|
||||||
})
|
|
||||||
var lastErr error
|
|
||||||
for _, entry := range entries {
|
|
||||||
img, decodeErr := decodeImageFile(filepath.Join(s.dir, entry.Name()))
|
|
||||||
if decodeErr == nil {
|
|
||||||
return img, nil
|
|
||||||
}
|
|
||||||
lastErr = decodeErr
|
|
||||||
}
|
|
||||||
if lastErr != nil {
|
|
||||||
return nil, lastErr
|
|
||||||
}
|
|
||||||
return nil, errors.New("PipeWire zatím neposkytl žádný snímek")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *pipeWireFrameStream) Close() {
|
|
||||||
if s.command.Process != nil {
|
|
||||||
_ = s.command.Process.Kill()
|
|
||||||
}
|
|
||||||
_, _ = s.command.Process.Wait()
|
|
||||||
entries, err := os.ReadDir(s.dir)
|
|
||||||
if err == nil {
|
|
||||||
for _, entry := range entries {
|
|
||||||
_ = os.Remove(filepath.Join(s.dir, entry.Name()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ = os.Remove(s.dir)
|
|
||||||
}
|
|
||||||
156
qr_support.go
156
qr_support.go
@ -474,151 +474,29 @@ func DecodeQRCodes(img image.Image) ([]string, error) {
|
|||||||
if img == nil {
|
if img == nil {
|
||||||
return nil, errors.New("prázdný obrázek")
|
return nil, errors.New("prázdný obrázek")
|
||||||
}
|
}
|
||||||
seen := make(map[string]struct{})
|
codes, err := goqr.Recognize(img)
|
||||||
texts := make([]string, 0)
|
if err != nil || len(codes) == 0 {
|
||||||
addCodes := func(codes []*goqr.QRData) {
|
if err == nil {
|
||||||
for _, code := range codes {
|
err = errors.New("QR kód nenalezen")
|
||||||
if code == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
text := string(code.Payload)
|
|
||||||
if text == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := seen[text]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[text] = struct{}{}
|
|
||||||
texts = append(texts, text)
|
|
||||||
}
|
}
|
||||||
}
|
return nil, err
|
||||||
recognize := func(candidate image.Image) bool {
|
|
||||||
codes, err := goqr.Recognize(candidate)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
before := len(texts)
|
|
||||||
addCodes(codes)
|
|
||||||
return len(texts) > before
|
|
||||||
}
|
|
||||||
scale := func(src image.Image, factor int) image.Image {
|
|
||||||
bounds := src.Bounds()
|
|
||||||
if factor <= 1 || bounds.Dx() <= 0 || bounds.Dy() <= 0 {
|
|
||||||
return src
|
|
||||||
}
|
|
||||||
dst := image.NewRGBA(image.Rect(0, 0, bounds.Dx()*factor, bounds.Dy()*factor))
|
|
||||||
for y := 0; y < dst.Bounds().Dy(); y++ {
|
|
||||||
for x := 0; x < dst.Bounds().Dx(); x++ {
|
|
||||||
dst.Set(x, y, src.At(bounds.Min.X+x/factor, bounds.Min.Y+y/factor))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
crop := func(src image.Image, x, y, width, height int) image.Image {
|
|
||||||
bounds := src.Bounds()
|
|
||||||
r := image.Rect(bounds.Min.X+x, bounds.Min.Y+y, bounds.Min.X+x+width, bounds.Min.Y+y+height)
|
|
||||||
if r.Min.X < bounds.Min.X || r.Min.Y < bounds.Min.Y || r.Max.X > bounds.Max.X || r.Max.Y > bounds.Max.Y {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := image.NewRGBA(image.Rect(0, 0, width, height))
|
|
||||||
draw.Draw(out, out.Bounds(), src, r.Min, draw.Src)
|
|
||||||
return out
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bounds := img.Bounds()
|
seen := make(map[string]struct{}, len(codes))
|
||||||
candidates := []image.Image{img}
|
texts := make([]string, 0, len(codes))
|
||||||
if max(bounds.Dx(), bounds.Dy()) <= 2400 {
|
for _, code := range codes {
|
||||||
candidates = append(candidates, scale(img, 2))
|
if code == nil {
|
||||||
}
|
continue
|
||||||
for _, candidate := range candidates {
|
|
||||||
recognize(candidate)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A selected chat region often contains several small QR codes. Scan four
|
|
||||||
// heavily overlapping tiles as well as the complete image so that one code
|
|
||||||
// does not hide another from the detector's global pass.
|
|
||||||
if bounds.Dx() >= 80 && bounds.Dy() >= 80 {
|
|
||||||
tileWidth := bounds.Dx() * 2 / 3
|
|
||||||
tileHeight := bounds.Dy() * 2 / 3
|
|
||||||
for _, origin := range [][2]int{
|
|
||||||
{0, 0},
|
|
||||||
{bounds.Dx() - tileWidth, 0},
|
|
||||||
{0, bounds.Dy() - tileHeight},
|
|
||||||
{bounds.Dx() - tileWidth, bounds.Dy() - tileHeight},
|
|
||||||
} {
|
|
||||||
tile := crop(img, origin[0], origin[1], tileWidth, tileHeight)
|
|
||||||
if tile == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
found := recognize(tile)
|
|
||||||
if !found && max(tile.Bounds().Dx(), tile.Bounds().Dy()) <= 1600 {
|
|
||||||
found = recognize(scale(tile, 2))
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
if text, err := DecodeQR(tile); err == nil && text != "" {
|
|
||||||
if _, exists := seen[text]; !exists {
|
|
||||||
seen[text] = struct{}{}
|
|
||||||
texts = append(texts, text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
text := string(code.Payload)
|
||||||
if len(texts) == 0 {
|
if text == "" {
|
||||||
if text, err := DecodeQR(img); err == nil && text != "" {
|
continue
|
||||||
texts = append(texts, text)
|
|
||||||
}
|
}
|
||||||
}
|
if _, ok := seen[text]; ok {
|
||||||
if len(texts) == 0 {
|
continue
|
||||||
return nil, errors.New("QR kód nenalezen")
|
|
||||||
}
|
|
||||||
return texts, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeQRCodesFast keeps the live scanner responsive. It avoids the expensive
|
|
||||||
// tiled DecodeQR fallback and only retries a reasonably sized frame once after
|
|
||||||
// nearest-neighbour upscaling.
|
|
||||||
func DecodeQRCodesFast(img image.Image) ([]string, error) {
|
|
||||||
if img == nil {
|
|
||||||
return nil, errors.New("prázdný obrázek")
|
|
||||||
}
|
|
||||||
decode := func(candidate image.Image) []string {
|
|
||||||
codes, err := goqr.Recognize(candidate)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
seen := make(map[string]struct{}, len(codes))
|
seen[text] = struct{}{}
|
||||||
texts := make([]string, 0, len(codes))
|
texts = append(texts, text)
|
||||||
for _, code := range codes {
|
|
||||||
if code == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
text := string(code.Payload)
|
|
||||||
if text == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, exists := seen[text]; exists {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[text] = struct{}{}
|
|
||||||
texts = append(texts, text)
|
|
||||||
}
|
|
||||||
return texts
|
|
||||||
}
|
|
||||||
|
|
||||||
texts := decode(img)
|
|
||||||
if len(texts) > 0 {
|
|
||||||
return texts, nil
|
|
||||||
}
|
|
||||||
bounds := img.Bounds()
|
|
||||||
if max(bounds.Dx(), bounds.Dy()) <= 1600 {
|
|
||||||
scaled := image.NewRGBA(image.Rect(0, 0, bounds.Dx()*2, bounds.Dy()*2))
|
|
||||||
for y := 0; y < scaled.Bounds().Dy(); y++ {
|
|
||||||
for x := 0; x < scaled.Bounds().Dx(); x++ {
|
|
||||||
scaled.Set(x, y, img.At(bounds.Min.X+x/2, bounds.Min.Y+y/2))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
texts = decode(scaled)
|
|
||||||
}
|
}
|
||||||
if len(texts) == 0 {
|
if len(texts) == 0 {
|
||||||
return nil, errors.New("QR kód nenalezen")
|
return nil, errors.New("QR kód nenalezen")
|
||||||
|
|||||||
@ -86,13 +86,8 @@ func captureScreenRegion() (image.Image, error) {
|
|||||||
func openScreenCaptureSession() (*screenCaptureSession, error) {
|
func openScreenCaptureSession() (*screenCaptureSession, error) {
|
||||||
var attempts []error
|
var attempts []error
|
||||||
isGNOME := strings.Contains(strings.ToLower(os.Getenv("XDG_CURRENT_DESKTOP")), "gnome")
|
isGNOME := strings.Contains(strings.ToLower(os.Getenv("XDG_CURRENT_DESKTOP")), "gnome")
|
||||||
if isGNOME {
|
|
||||||
session, sessionErr := openPortalScreenCastSession()
|
if _, err := exec.LookPath("flameshot"); err == nil {
|
||||||
if sessionErr == nil {
|
|
||||||
return session, nil
|
|
||||||
}
|
|
||||||
attempts = append(attempts, fmt.Errorf("GNOME ScreenCast: %w", sessionErr))
|
|
||||||
} else if _, err := exec.LookPath("flameshot"); err == nil {
|
|
||||||
session, sessionErr := openFlameshotSession()
|
session, sessionErr := openFlameshotSession()
|
||||||
if sessionErr == nil {
|
if sessionErr == nil {
|
||||||
return session, nil
|
return session, nil
|
||||||
@ -150,7 +145,7 @@ func openScreenCaptureSession() (*screenCaptureSession, error) {
|
|||||||
return nil, fmt.Errorf("živé snímání oblasti selhalo: %w", errors.Join(attempts...))
|
return nil, fmt.Errorf("živé snímání oblasti selhalo: %w", errors.Join(attempts...))
|
||||||
}
|
}
|
||||||
if isGNOME {
|
if isGNOME {
|
||||||
return nil, errors.New("GNOME Wayland vyžaduje pro živé sledování desktopový ScreenCast portál a PipeWire. Jednorázové snímání oblasti zůstává dostupné.")
|
return nil, errors.New("GNOME Wayland nepovoluje živé snímání libovolné oblasti přes gnome-screenshot; pro živé sledování nainstalujte Flameshot s podporou --last-region. Jednorázové snímání oblasti zůstává dostupné.")
|
||||||
}
|
}
|
||||||
return nil, errors.New("nenalezen nástroj pro živé snímání vybrané oblasti (Flameshot, grim+slurp nebo slop+maim)")
|
return nil, errors.New("nenalezen nástroj pro živé snímání vybrané oblasti (Flameshot, grim+slurp nebo slop+maim)")
|
||||||
}
|
}
|
||||||
|
|||||||
48
ui.go
48
ui.go
@ -14,7 +14,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@ -108,9 +107,7 @@ func (simpleTheme) Size(n fyne.ThemeSizeName) float32 { return theme.Defau
|
|||||||
// Facade interface
|
// Facade interface
|
||||||
type ServiceFacade interface {
|
type ServiceFacade interface {
|
||||||
Encrypt(msg, peer string) (string, error)
|
Encrypt(msg, peer string) (string, error)
|
||||||
EncryptChat(msg, peer string) (string, error)
|
|
||||||
Decrypt(json string) (string, error)
|
Decrypt(json string) (string, error)
|
||||||
DecryptChat(json string) (DecryptedChatMessage, error)
|
|
||||||
PublicPEM() string
|
PublicPEM() string
|
||||||
PublicCert() string
|
PublicCert() string
|
||||||
ListContacts() ([]Contact, error)
|
ListContacts() ([]Contact, error)
|
||||||
@ -118,7 +115,6 @@ type ServiceFacade interface {
|
|||||||
DeleteContact(id string) error
|
DeleteContact(id string) error
|
||||||
ListChatMessages() ([]ChatMessage, error)
|
ListChatMessages() ([]ChatMessage, error)
|
||||||
AppendChatMessage(direction, text, payload string) (ChatMessage, bool, error)
|
AppendChatMessage(direction, text, payload string) (ChatMessage, bool, error)
|
||||||
AppendScannedChatMessage(direction, text, payload string, createdAt time.Time) (ChatMessage, bool, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clipboard helpers
|
// Clipboard helpers
|
||||||
@ -936,6 +932,9 @@ func buildChatScannerTab(parts *uiParts, svc ServiceFacade) fyne.CanvasObject {
|
|||||||
} else {
|
} else {
|
||||||
results = make([]chatScanResult, 0, len(storedMessages))
|
results = make([]chatScanResult, 0, len(storedMessages))
|
||||||
for _, message := range storedMessages {
|
for _, message := range storedMessages {
|
||||||
|
if message.Direction != chatUIDirectionIncoming {
|
||||||
|
continue
|
||||||
|
}
|
||||||
results = append(results, chatScanResult{
|
results = append(results, chatScanResult{
|
||||||
direction: message.Direction,
|
direction: message.Direction,
|
||||||
plaintext: message.Text,
|
plaintext: message.Text,
|
||||||
@ -943,7 +942,6 @@ func buildChatScannerTab(parts *uiParts, svc ServiceFacade) fyne.CanvasObject {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sort.SliceStable(results, func(i, j int) bool { return results[i].createdAt.Before(results[j].createdAt) })
|
|
||||||
var list *widget.List
|
var list *widget.List
|
||||||
var chatListHost *fyne.Container
|
var chatListHost *fyne.Container
|
||||||
|
|
||||||
@ -989,24 +987,23 @@ func buildChatScannerTab(parts *uiParts, svc ServiceFacade) fyne.CanvasObject {
|
|||||||
if !isEncryptedChatPayload(payload) {
|
if !isEncryptedChatPayload(payload) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
message, err := svc.DecryptChat(payload)
|
plaintext, err := svc.Decrypt(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
decoded = append(decoded, chatScanResult{
|
decoded = append(decoded, chatScanResult{
|
||||||
direction: message.Direction,
|
direction: chatUIDirectionIncoming,
|
||||||
payload: payload,
|
payload: payload,
|
||||||
plaintext: message.Text,
|
plaintext: plaintext,
|
||||||
createdAt: message.CreatedAt,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return decoded
|
return decoded
|
||||||
}
|
}
|
||||||
|
|
||||||
persistScannedMessages := func(decoded []chatScanResult) []chatScanResult {
|
persistIncoming := func(decoded []chatScanResult) []chatScanResult {
|
||||||
accepted := make([]chatScanResult, 0, len(decoded))
|
accepted := make([]chatScanResult, 0, len(decoded))
|
||||||
for _, item := range decoded {
|
for _, item := range decoded {
|
||||||
message, added, err := svc.AppendScannedChatMessage(item.direction, item.plaintext, item.payload, item.createdAt)
|
message, added, err := svc.AppendChatMessage(chatUIDirectionIncoming, item.plaintext, item.payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -1022,10 +1019,9 @@ func buildChatScannerTab(parts *uiParts, svc ServiceFacade) fyne.CanvasObject {
|
|||||||
applyResults := func(payloads []string) {
|
applyResults := func(payloads []string) {
|
||||||
go func() {
|
go func() {
|
||||||
decoded := decryptPayloads(payloads)
|
decoded := decryptPayloads(payloads)
|
||||||
accepted := persistScannedMessages(decoded)
|
accepted := persistIncoming(decoded)
|
||||||
fyne.Do(func() {
|
fyne.Do(func() {
|
||||||
results = append(results, accepted...)
|
results = append(results, accepted...)
|
||||||
sort.SliceStable(results, func(i, j int) bool { return results[i].createdAt.Before(results[j].createdAt) })
|
|
||||||
refreshChatList()
|
refreshChatList()
|
||||||
status.SetText(fmt.Sprintf("Načteno %d QR kódů, úspěšně dešifrováno %d zpráv, přidáno %d nových zpráv.", len(payloads), len(decoded), len(accepted)))
|
status.SetText(fmt.Sprintf("Načteno %d QR kódů, úspěšně dešifrováno %d zpráv, přidáno %d nových zpráv.", len(payloads), len(decoded), len(accepted)))
|
||||||
})
|
})
|
||||||
@ -1082,11 +1078,7 @@ func buildChatScannerTab(parts *uiParts, svc ServiceFacade) fyne.CanvasObject {
|
|||||||
stopLive = nil
|
stopLive = nil
|
||||||
status.SetText("Živé sledování zastaveno.")
|
status.SetText("Živé sledování zastaveno.")
|
||||||
}
|
}
|
||||||
if strings.Contains(strings.ToLower(os.Getenv("XDG_CURRENT_DESKTOP")), "gnome") {
|
status.SetText("Vybírám oblast pro živé sledování…")
|
||||||
status.SetText("V systémovém dialogu vyberte okno nebo monitor s chatem.")
|
|
||||||
} else {
|
|
||||||
status.SetText("Vybírám oblast pro živé sledování…")
|
|
||||||
}
|
|
||||||
go func() {
|
go func() {
|
||||||
session, err := openScreenCaptureSession()
|
session, err := openScreenCaptureSession()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1099,9 +1091,9 @@ func buildChatScannerTab(parts *uiParts, svc ServiceFacade) fyne.CanvasObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer session.Close()
|
defer session.Close()
|
||||||
ticker := time.NewTicker(time.Second)
|
ticker := time.NewTicker(200 * time.Millisecond)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
fyne.Do(func() { status.SetText("Živé sledování běží. QR kódy se kontrolují jednou za sekundu.") })
|
fyne.Do(func() { status.SetText("Živé sledování běží. QR kódy se kontrolují 5× za sekundu.") })
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-stopRequested:
|
case <-stopRequested:
|
||||||
@ -1112,16 +1104,15 @@ func buildChatScannerTab(parts *uiParts, svc ServiceFacade) fyne.CanvasObject {
|
|||||||
fyne.Do(func() { status.SetText("Snímání selhalo: " + captureErr.Error()) })
|
fyne.Do(func() { status.SetText("Snímání selhalo: " + captureErr.Error()) })
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
payloads, decodeErr := DecodeQRCodesFast(img)
|
payloads, decodeErr := DecodeQRCodes(img)
|
||||||
if decodeErr != nil {
|
if decodeErr != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
decoded := persistScannedMessages(decryptPayloads(payloads))
|
decoded := persistIncoming(decryptPayloads(payloads))
|
||||||
fyne.Do(func() {
|
fyne.Do(func() {
|
||||||
for _, item := range decoded {
|
for _, item := range decoded {
|
||||||
results = append(results, item)
|
results = append(results, item)
|
||||||
}
|
}
|
||||||
sort.SliceStable(results, func(i, j int) bool { return results[i].createdAt.Before(results[j].createdAt) })
|
|
||||||
refreshChatList()
|
refreshChatList()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1224,7 +1215,7 @@ func buildChatScannerTab(parts *uiParts, svc ServiceFacade) fyne.CanvasObject {
|
|||||||
messageEntry.SetPlaceHolder("Napište zprávu, kterou chcete poslat…")
|
messageEntry.SetPlaceHolder("Napište zprávu, kterou chcete poslat…")
|
||||||
outgoingQR := canvas.NewImageFromImage(nil)
|
outgoingQR := canvas.NewImageFromImage(nil)
|
||||||
outgoingQR.FillMode = canvas.ImageFillContain
|
outgoingQR.FillMode = canvas.ImageFillContain
|
||||||
outgoingQR.SetMinSize(fyne.NewSize(200, 200))
|
outgoingQR.SetMinSize(fyne.NewSize(220, 220))
|
||||||
outgoingStatus := widget.NewLabel("QR se zobrazí po zašifrování zprávy.")
|
outgoingStatus := widget.NewLabel("QR se zobrazí po zašifrování zprávy.")
|
||||||
outgoingStatus.Wrapping = fyne.TextWrapWord
|
outgoingStatus.Wrapping = fyne.TextWrapWord
|
||||||
var outgoingPayload string
|
var outgoingPayload string
|
||||||
@ -1375,7 +1366,7 @@ func buildChatScannerTab(parts *uiParts, svc ServiceFacade) fyne.CanvasObject {
|
|||||||
}
|
}
|
||||||
outgoingStatus.SetText("QR připravím po krátké pauze…")
|
outgoingStatus.SetText("QR připravím po krátké pauze…")
|
||||||
outgoingTimer = time.AfterFunc(450*time.Millisecond, func() {
|
outgoingTimer = time.AfterFunc(450*time.Millisecond, func() {
|
||||||
payload, err := svc.EncryptChat(message, peer)
|
payload, err := svc.Encrypt(message, peer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fyne.Do(func() {
|
fyne.Do(func() {
|
||||||
if version == outgoingVersion.Load() {
|
if version == outgoingVersion.Load() {
|
||||||
@ -1445,11 +1436,10 @@ func buildChatScannerTab(parts *uiParts, svc ServiceFacade) fyne.CanvasObject {
|
|||||||
outgoingStatus,
|
outgoingStatus,
|
||||||
)
|
)
|
||||||
outgoingPreview := container.NewVBox(
|
outgoingPreview := container.NewVBox(
|
||||||
widget.NewLabel("Výsledný QR kód"),
|
container.NewHBox(widget.NewLabel("Výsledný QR kód"), layout.NewSpacer(), copyOutgoingBtn, copyPayloadBtn),
|
||||||
container.NewGridWithColumns(2, copyOutgoingBtn, copyPayloadBtn),
|
|
||||||
container.NewCenter(outgoingQR),
|
container.NewCenter(outgoingQR),
|
||||||
)
|
)
|
||||||
outgoing := container.NewScroll(container.NewVBox(outgoingForm, widget.NewSeparator(), outgoingPreview))
|
outgoing := container.NewScroll(container.NewHSplit(outgoingForm, outgoingPreview))
|
||||||
refreshChatList()
|
refreshChatList()
|
||||||
|
|
||||||
intro := widget.NewLabel("Načtěte QR zprávu přímo z obrazovky. Dešifrovaný text zůstává pouze v tomto trezoru.")
|
intro := widget.NewLabel("Načtěte QR zprávu přímo z obrazovky. Dešifrovaný text zůstává pouze v tomto trezoru.")
|
||||||
@ -1458,7 +1448,7 @@ func buildChatScannerTab(parts *uiParts, svc ServiceFacade) fyne.CanvasObject {
|
|||||||
widget.NewLabelWithStyle("Skenovat chat", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
widget.NewLabelWithStyle("Skenovat chat", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||||
intro,
|
intro,
|
||||||
)
|
)
|
||||||
split := container.NewHSplit(incoming, outgoing)
|
split := container.NewVSplit(incoming, outgoing)
|
||||||
split.SetOffset(0.58)
|
split.SetOffset(0.58)
|
||||||
return container.NewBorder(header, nil, nil, nil, split)
|
return container.NewBorder(header, nil, nil, nil, split)
|
||||||
}
|
}
|
||||||
|
|||||||
111
vault_service.go
111
vault_service.go
@ -70,25 +70,15 @@ func (v *VaultService) Encrypt(message, peerPEMorCert string) (string, error) {
|
|||||||
return encryptHybrid(v.priv, message, peerPEMorCert)
|
return encryptHybrid(v.priv, message, peerPEMorCert)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VaultService) EncryptChat(message, peerPEMorCert string) (string, error) {
|
|
||||||
return encryptChatHybrid(v.priv, message, peerPEMorCert)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrypt provede rozšifrování.
|
// Decrypt provede rozšifrování.
|
||||||
func (v *VaultService) Decrypt(payload string) (string, error) { return decryptHybrid(v.priv, payload) }
|
func (v *VaultService) Decrypt(payload string) (string, error) { return decryptHybrid(v.priv, payload) }
|
||||||
|
|
||||||
func (v *VaultService) DecryptChat(payload string) (DecryptedChatMessage, error) {
|
|
||||||
return decryptChatHybrid(v.priv, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Lokální helpery (duplikace z encrypt.Service, zredukované) ---
|
// --- Lokální helpery (duplikace z encrypt.Service, zredukované) ---
|
||||||
|
|
||||||
type hybridEnvelope struct {
|
type hybridEnvelope struct {
|
||||||
EK string `json:"ek"`
|
EK string `json:"ek"`
|
||||||
N string `json:"n"`
|
N string `json:"n"`
|
||||||
CT string `json:"ct"`
|
CT string `json:"ct"`
|
||||||
SelfEK string `json:"sek,omitempty"`
|
|
||||||
SentAt string `json:"at,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Contacts management ---
|
// --- Contacts management ---
|
||||||
@ -114,12 +104,6 @@ type ChatMessage struct {
|
|||||||
PayloadHash string `json:"payloadHash"`
|
PayloadHash string `json:"payloadHash"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DecryptedChatMessage struct {
|
|
||||||
Direction string
|
|
||||||
Text string
|
|
||||||
CreatedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *VaultService) ListContacts() ([]Contact, error) {
|
func (v *VaultService) ListContacts() ([]Contact, error) {
|
||||||
var list []Contact
|
var list []Contact
|
||||||
if !v.store.Has(contactsKey) {
|
if !v.store.Has(contactsKey) {
|
||||||
@ -151,17 +135,6 @@ func (v *VaultService) listChatMessagesLocked() ([]ChatMessage, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *VaultService) AppendChatMessage(direction, text, payload string) (ChatMessage, bool, error) {
|
func (v *VaultService) AppendChatMessage(direction, text, payload string) (ChatMessage, bool, error) {
|
||||||
return v.appendChatMessage(direction, text, payload, time.Now().UTC())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *VaultService) AppendScannedChatMessage(direction, text, payload string, createdAt time.Time) (ChatMessage, bool, error) {
|
|
||||||
if createdAt.IsZero() {
|
|
||||||
createdAt = time.Now().UTC()
|
|
||||||
}
|
|
||||||
return v.appendChatMessage(direction, text, payload, createdAt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *VaultService) appendChatMessage(direction, text, payload string, createdAt time.Time) (ChatMessage, bool, error) {
|
|
||||||
if direction != persistedChatDirectionIn && direction != persistedChatDirectionOut {
|
if direction != persistedChatDirectionIn && direction != persistedChatDirectionOut {
|
||||||
return ChatMessage{}, false, fmt.Errorf("invalid chat message direction %q", direction)
|
return ChatMessage{}, false, fmt.Errorf("invalid chat message direction %q", direction)
|
||||||
}
|
}
|
||||||
@ -188,7 +161,7 @@ func (v *VaultService) appendChatMessage(direction, text, payload string, create
|
|||||||
message := ChatMessage{
|
message := ChatMessage{
|
||||||
Direction: direction,
|
Direction: direction,
|
||||||
Text: text,
|
Text: text,
|
||||||
CreatedAt: createdAt.UTC(),
|
CreatedAt: time.Now().UTC(),
|
||||||
PayloadHash: payloadHash,
|
PayloadHash: payloadHash,
|
||||||
}
|
}
|
||||||
messages = append(messages, message)
|
messages = append(messages, message)
|
||||||
@ -287,17 +260,6 @@ func extractCN(pemText string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func encryptHybrid(priv *rsa.PrivateKey, message, peerPEMorCert string) (string, error) {
|
func encryptHybrid(priv *rsa.PrivateKey, message, peerPEMorCert string) (string, error) {
|
||||||
return encryptHybridEnvelope(nil, message, peerPEMorCert)
|
|
||||||
}
|
|
||||||
|
|
||||||
func encryptChatHybrid(priv *rsa.PrivateKey, message, peerPEMorCert string) (string, error) {
|
|
||||||
if priv == nil {
|
|
||||||
return "", errors.New("missing private key")
|
|
||||||
}
|
|
||||||
return encryptHybridEnvelope(&priv.PublicKey, message, peerPEMorCert)
|
|
||||||
}
|
|
||||||
|
|
||||||
func encryptHybridEnvelope(selfPublicKey *rsa.PublicKey, message, peerPEMorCert string) (string, error) {
|
|
||||||
pubKey, err := encrypt.ParsePeerPublicKey(peerPEMorCert)
|
pubKey, err := encrypt.ParsePeerPublicKey(peerPEMorCert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@ -323,76 +285,27 @@ func encryptHybridEnvelope(selfPublicKey *rsa.PublicKey, message, peerPEMorCert
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
env := hybridEnvelope{
|
env := hybridEnvelope{EK: base64.StdEncoding.EncodeToString(ek), N: base64.StdEncoding.EncodeToString(nonce), CT: base64.StdEncoding.EncodeToString(ct)}
|
||||||
EK: base64.StdEncoding.EncodeToString(ek),
|
|
||||||
N: base64.StdEncoding.EncodeToString(nonce),
|
|
||||||
CT: base64.StdEncoding.EncodeToString(ct),
|
|
||||||
}
|
|
||||||
if selfPublicKey != nil {
|
|
||||||
selfEK, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, selfPublicKey, aesKey, []byte{})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
env.SelfEK = base64.StdEncoding.EncodeToString(selfEK)
|
|
||||||
env.SentAt = time.Now().UTC().Format(time.RFC3339Nano)
|
|
||||||
}
|
|
||||||
out, _ := json.MarshalIndent(env, "", " ")
|
out, _ := json.MarshalIndent(env, "", " ")
|
||||||
return string(out), nil
|
return string(out), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func decryptHybrid(priv *rsa.PrivateKey, payload string) (string, error) {
|
func decryptHybrid(priv *rsa.PrivateKey, payload string) (string, error) {
|
||||||
env, nonce, ct, err := decodeHybridEnvelope(payload)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return decryptHybridPayload(priv, env.EK, nonce, ct)
|
|
||||||
}
|
|
||||||
|
|
||||||
func decryptChatHybrid(priv *rsa.PrivateKey, payload string) (DecryptedChatMessage, error) {
|
|
||||||
env, nonce, ct, err := decodeHybridEnvelope(payload)
|
|
||||||
if err != nil {
|
|
||||||
return DecryptedChatMessage{}, err
|
|
||||||
}
|
|
||||||
createdAt := time.Time{}
|
|
||||||
if env.SentAt != "" {
|
|
||||||
createdAt, _ = time.Parse(time.RFC3339Nano, env.SentAt)
|
|
||||||
}
|
|
||||||
if env.SelfEK != "" {
|
|
||||||
plaintext, selfErr := decryptHybridPayload(priv, env.SelfEK, nonce, ct)
|
|
||||||
if selfErr == nil {
|
|
||||||
return DecryptedChatMessage{Direction: persistedChatDirectionOut, Text: plaintext, CreatedAt: createdAt}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
plaintext, err := decryptHybridPayload(priv, env.EK, nonce, ct)
|
|
||||||
if err != nil {
|
|
||||||
return DecryptedChatMessage{}, err
|
|
||||||
}
|
|
||||||
return DecryptedChatMessage{Direction: persistedChatDirectionIn, Text: plaintext, CreatedAt: createdAt}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeHybridEnvelope(payload string) (hybridEnvelope, []byte, []byte, error) {
|
|
||||||
var env hybridEnvelope
|
var env hybridEnvelope
|
||||||
if err := json.Unmarshal([]byte(payload), &env); err != nil {
|
if err := json.Unmarshal([]byte(payload), &env); err != nil {
|
||||||
return hybridEnvelope{}, nil, nil, fmt.Errorf("invalid JSON: %w", err)
|
return "", fmt.Errorf("invalid JSON: %w", err)
|
||||||
|
}
|
||||||
|
ek, err := base64.StdEncoding.DecodeString(env.EK)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("ek b64: %w", err)
|
||||||
}
|
}
|
||||||
nonce, err := base64.StdEncoding.DecodeString(env.N)
|
nonce, err := base64.StdEncoding.DecodeString(env.N)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return hybridEnvelope{}, nil, nil, fmt.Errorf("n b64: %w", err)
|
return "", fmt.Errorf("n b64: %w", err)
|
||||||
}
|
}
|
||||||
ct, err := base64.StdEncoding.DecodeString(env.CT)
|
ct, err := base64.StdEncoding.DecodeString(env.CT)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return hybridEnvelope{}, nil, nil, fmt.Errorf("ct b64: %w", err)
|
return "", fmt.Errorf("ct b64: %w", err)
|
||||||
}
|
|
||||||
return env, nonce, ct, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func decryptHybridPayload(priv *rsa.PrivateKey, encodedKey string, nonce, ct []byte) (string, error) {
|
|
||||||
if priv == nil {
|
|
||||||
return "", errors.New("missing private key")
|
|
||||||
}
|
|
||||||
ek, err := base64.StdEncoding.DecodeString(encodedKey)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("ek b64: %w", err)
|
|
||||||
}
|
}
|
||||||
aesKey, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, priv, ek, []byte{})
|
aesKey, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, priv, ek, []byte{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user