mirror of
https://github.com/Quad4-Software/Reticulum-Go
synced 2026-08-29 23:48:44 -04:00
179 lines
5.4 KiB
Go
179 lines
5.4 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright (c) 2024-2026 Quad4.io
|
|
|
|
package selfcheck
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
|
|
"quad4/reticulum-go/pkg/cryptography"
|
|
"quad4/reticulum-go/pkg/identity"
|
|
"quad4/reticulum-go/pkg/packet"
|
|
)
|
|
|
|
func checkRuntime() []Result {
|
|
out := make([]Result, 0, 3)
|
|
out = append(out, result("runtime/platform", SeverityPass,
|
|
fmt.Sprintf("%s/%s %s", runtime.GOOS, runtime.GOARCH, goVersion())))
|
|
|
|
cgo := "disabled"
|
|
if os.Getenv("CGO_ENABLED") == "1" {
|
|
cgo = "enabled"
|
|
} else if os.Getenv("CGO_ENABLED") == "" {
|
|
cgo = "default"
|
|
}
|
|
out = append(out, result("runtime/cgo", SeverityPass, cgo))
|
|
|
|
dir, err := os.MkdirTemp("", "rns-selfcheck-runtime-*")
|
|
if err != nil {
|
|
out = append(out, result("runtime/tempdir", SeverityFail, err.Error()))
|
|
return out
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
probe := filepath.Join(dir, "probe")
|
|
if err := os.WriteFile(probe, []byte("ok"), fileModePrivate); err != nil {
|
|
out = append(out, result("runtime/tempdir", SeverityFail, err.Error()))
|
|
return out
|
|
}
|
|
out = append(out, result("runtime/tempdir", SeverityPass, dir))
|
|
return out
|
|
}
|
|
|
|
func checkCrypto() []Result {
|
|
out := make([]Result, 0, 3)
|
|
|
|
aPriv, aPub, err := cryptography.GenerateKeyPair()
|
|
if err != nil {
|
|
out = append(out, result("crypto/x25519", SeverityFail, err.Error()))
|
|
} else {
|
|
bPriv, bPub, err := cryptography.GenerateKeyPair()
|
|
if err != nil {
|
|
out = append(out, result("crypto/x25519", SeverityFail, err.Error()))
|
|
} else {
|
|
s1, err1 := cryptography.DeriveSharedSecret(aPriv, bPub)
|
|
s2, err2 := cryptography.DeriveSharedSecret(bPriv, aPub)
|
|
if err1 != nil || err2 != nil {
|
|
out = append(out, result("crypto/x25519", SeverityFail, fmt.Sprintf("%v %v", err1, err2)))
|
|
} else if !bytes.Equal(s1, s2) {
|
|
out = append(out, result("crypto/x25519", SeverityFail, "shared secrets differ"))
|
|
} else {
|
|
out = append(out, result("crypto/x25519", SeverityPass, "shared secret match"))
|
|
}
|
|
}
|
|
}
|
|
|
|
pub, priv, err := cryptography.GenerateSigningKeyPair()
|
|
if err != nil {
|
|
out = append(out, result("crypto/ed25519", SeverityFail, err.Error()))
|
|
} else {
|
|
msg := []byte("reticulum-go self-check")
|
|
sig := cryptography.Sign(priv, msg)
|
|
if !cryptography.Verify(pub, msg, sig) {
|
|
out = append(out, result("crypto/ed25519", SeverityFail, "verify failed"))
|
|
} else {
|
|
out = append(out, result("crypto/ed25519", SeverityPass, "sign and verify"))
|
|
}
|
|
}
|
|
|
|
key, err := cryptography.GenerateAES256Key()
|
|
if err != nil {
|
|
out = append(out, result("crypto/aes256-cbc", SeverityFail, err.Error()))
|
|
return out
|
|
}
|
|
plain := []byte("self-check plaintext")
|
|
ct, err := cryptography.EncryptAES256CBC(key, plain)
|
|
if err != nil {
|
|
out = append(out, result("crypto/aes256-cbc", SeverityFail, err.Error()))
|
|
return out
|
|
}
|
|
pt, err := cryptography.DecryptAES256CBC(key, ct)
|
|
if err != nil {
|
|
out = append(out, result("crypto/aes256-cbc", SeverityFail, err.Error()))
|
|
return out
|
|
}
|
|
if !bytes.Equal(pt, plain) {
|
|
out = append(out, result("crypto/aes256-cbc", SeverityFail, "plaintext mismatch"))
|
|
return out
|
|
}
|
|
out = append(out, result("crypto/aes256-cbc", SeverityPass, "encrypt and decrypt"))
|
|
return out
|
|
}
|
|
|
|
func checkIdentityFile(opts Options) []Result {
|
|
parent := opts.WorkDir
|
|
if parent == "" {
|
|
parent = os.TempDir()
|
|
}
|
|
dir, err := os.MkdirTemp(parent, "rns-selfcheck-id-*")
|
|
if err != nil {
|
|
return []Result{result(nameIdentityFile, SeverityFail, err.Error())}
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
id, err := identity.New()
|
|
if err != nil {
|
|
return []Result{result(nameIdentityFile, SeverityFail, err.Error())}
|
|
}
|
|
defer id.Close()
|
|
|
|
path := filepath.Join(dir, "identity")
|
|
if err := id.ToFile(path); err != nil {
|
|
return []Result{result(nameIdentityFile, SeverityFail, "save: "+err.Error())}
|
|
}
|
|
loaded, err := identity.FromFile(path)
|
|
if err != nil {
|
|
return []Result{result(nameIdentityFile, SeverityFail, "load: "+err.Error())}
|
|
}
|
|
defer loaded.Close()
|
|
if id.GetHexHash() != loaded.GetHexHash() {
|
|
return []Result{result(nameIdentityFile, SeverityFail, "hash mismatch after reload")}
|
|
}
|
|
return []Result{result(nameIdentityFile, SeverityPass, id.GetHexHash())}
|
|
}
|
|
|
|
func checkPacket() Result {
|
|
p := &packet.Packet{
|
|
HeaderType: packet.HeaderType1,
|
|
PacketType: packet.PacketTypeData,
|
|
DestinationHash: make([]byte, packet.TruncatedHashLength),
|
|
Data: []byte("self-check"),
|
|
}
|
|
if err := p.Pack(); err != nil {
|
|
return result("packet/roundtrip", SeverityFail, "pack: "+err.Error())
|
|
}
|
|
raw := append([]byte(nil), p.Raw...)
|
|
p2 := &packet.Packet{Raw: raw}
|
|
if err := p2.Unpack(); err != nil {
|
|
return result("packet/roundtrip", SeverityFail, "unpack: "+err.Error())
|
|
}
|
|
if !bytes.Equal(p2.Data, p.Data) {
|
|
return result("packet/roundtrip", SeverityFail, "data mismatch")
|
|
}
|
|
return result("packet/roundtrip", SeverityPass, fmt.Sprintf("%d bytes", len(raw)))
|
|
}
|
|
|
|
func checkBinaryCLI(opts Options) Result {
|
|
path := opts.BinaryPath
|
|
if path == "" {
|
|
return result("binary/cli", SeveritySkip, "BinaryPath not set")
|
|
}
|
|
abs, err := filepath.Abs(path)
|
|
if err != nil {
|
|
return result("binary/cli", SeverityFail, err.Error())
|
|
}
|
|
path = abs
|
|
if _, err := os.Stat(path); err != nil {
|
|
return result("binary/cli", SeverityFail, err.Error())
|
|
}
|
|
cmd := exec.Command(path, "--version") // #nosec G204 -- BinaryPath from CLI or CI wrapper
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return result("binary/cli", SeverityFail, fmt.Sprintf("%v: %s", err, bytes.TrimSpace(out)))
|
|
}
|
|
return result("binary/cli", SeverityPass, string(bytes.TrimSpace(out)))
|
|
}
|