Fix merge conflict

This commit is contained in:
eyedeekay
2024-11-08 15:03:32 -05:00
7 changed files with 600 additions and 30 deletions

View File

@ -10,6 +10,7 @@ import (
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
"github.com/sirupsen/logrus"
"io" "io"
"net" "net"
"os" "os"
@ -37,6 +38,7 @@ type I2PKeys struct {
// Creates I2PKeys from an I2PAddr and a public/private keypair string (as // Creates I2PKeys from an I2PAddr and a public/private keypair string (as
// generated by String().) // generated by String().)
func NewKeys(addr I2PAddr, both string) I2PKeys { func NewKeys(addr I2PAddr, both string) I2PKeys {
log.WithField("addr", addr).Debug("Creating new I2PKeys")
return I2PKeys{addr, both} return I2PKeys{addr, both}
} }
@ -45,54 +47,98 @@ func NewKeys(addr I2PAddr, both string) I2PKeys {
func fileExists(filename string) (bool, error) { func fileExists(filename string) (bool, error) {
info, err := os.Stat(filename) info, err := os.Stat(filename)
if os.IsNotExist(err) { if os.IsNotExist(err) {
log.WithField("filename", filename).Debug("File does not exist")
return false, nil return false, nil
} else if err != nil { } else if err != nil {
return false, err log.WithError(err).WithField("filename", filename).Error("Error checking file existence")
return false, fmt.Errorf("error checking file existence: %w", err)
}
exists := !info.IsDir()
if exists {
log.WithField("filename", filename).Debug("File exists")
} else {
log.WithField("filename", filename).Debug("File is a directory")
} }
return !info.IsDir(), nil return !info.IsDir(), nil
} }
// load keys from non standard format // LoadKeysIncompat loads keys from a non-standard format
func LoadKeysIncompat(r io.Reader) (k I2PKeys, err error) { func LoadKeysIncompat(r io.Reader) (I2PKeys, error) {
log.Debug("Loading keys from reader")
var buff bytes.Buffer var buff bytes.Buffer
_, err = io.Copy(&buff, r) _, err := io.Copy(&buff, r)
if err == nil { if err != nil {
parts := strings.Split(buff.String(), "\n") log.WithError(err).Error("Error copying from reader, did not load keys")
k = I2PKeys{I2PAddr(parts[0]), parts[1]} return I2PKeys{}, fmt.Errorf("error copying from reader: %w", err)
} }
return
parts := strings.Split(buff.String(), "\n")
if len(parts) < 2 {
err := errors.New("invalid key format: not enough data")
log.WithError(err).Error("Error parsing keys")
return I2PKeys{}, err
}
k := I2PKeys{I2PAddr(parts[0]), parts[1]}
log.WithField("keys", k).Debug("Loaded keys")
return k, nil
} }
// load keys from non-standard format by specifying a text file. // load keys from non-standard format by specifying a text file.
// If the file does not exist, generate keys, otherwise, fail // If the file does not exist, generate keys, otherwise, fail
// closed. // closed.
func LoadKeys(r string) (I2PKeys, error) { func LoadKeys(r string) (I2PKeys, error) {
log.WithField("filename", r).Debug("Loading keys from file")
exists, err := fileExists(r) exists, err := fileExists(r)
if err != nil { if err != nil {
log.WithError(err).Error("Error checking if file exists")
return I2PKeys{}, err return I2PKeys{}, err
} }
if exists { if !exists {
// File doesn't exist so we'll generate new keys
log.WithError(err).Debug("File does not exist, attempting to generate new keys")
k, err := NewDestination()
if err != nil {
log.WithError(err).Error("Error generating new keys")
return I2PKeys{}, err
}
// Save the new keys to the file
err = StoreKeys(*k, r)
if err != nil {
log.WithError(err).Error("Error saving new keys to file")
return I2PKeys{}, err
}
return *k, nil
}
fi, err := os.Open(r) fi, err := os.Open(r)
if err != nil { if err != nil {
return I2PKeys{}, err log.WithError(err).WithField("filename", r).Error("Error opening file")
return I2PKeys{}, fmt.Errorf("error opening file: %w", err)
} }
defer fi.Close() defer fi.Close()
log.WithField("filename", r).Debug("File opened successfully")
return LoadKeysIncompat(fi) return LoadKeysIncompat(fi)
}
return I2PKeys{}, err
} }
// store keys in non standard format // store keys in non standard format
func StoreKeysIncompat(k I2PKeys, w io.Writer) (err error) { func StoreKeysIncompat(k I2PKeys, w io.Writer) error {
_, err = io.WriteString(w, k.Address.Base64()+"\n"+k.Both) log.Debug("Storing keys")
return _, err := io.WriteString(w, k.Address.Base64()+"\n"+k.Both)
if err != nil {
log.WithError(err).Error("Error writing keys")
return fmt.Errorf("error writing keys: %w", err)
}
log.WithField("keys", k).Debug("Keys stored successfully")
return nil
} }
func StoreKeys(k I2PKeys, r string) error { func StoreKeys(k I2PKeys, r string) error {
log.WithField("filename", r).Debug("Storing keys to file")
if _, err := os.Stat(r); err != nil { if _, err := os.Stat(r); err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
log.WithField("filename", r).Debug("File does not exist, creating new file")
fi, err := os.Create(r) fi, err := os.Create(r)
if err != nil { if err != nil {
log.WithError(err).Error("Error creating file")
return err return err
} }
defer fi.Close() defer fi.Close()
@ -101,6 +147,7 @@ func StoreKeys(k I2PKeys, r string) error {
} }
fi, err := os.Open(r) fi, err := os.Open(r)
if err != nil { if err != nil {
log.WithError(err).Error("Error opening file")
return err return err
} }
defer fi.Close() defer fi.Close()
@ -121,10 +168,12 @@ func (k I2PKeys) Public() crypto.PublicKey {
} }
func (k I2PKeys) Private() []byte { func (k I2PKeys) Private() []byte {
log.Debug("Extracting private key")
src := strings.Split(k.String(), k.Addr().String())[0] src := strings.Split(k.String(), k.Addr().String())[0]
var dest []byte var dest []byte
_, err := i2pB64enc.Decode(dest, []byte(src)) _, err := i2pB64enc.Decode(dest, []byte(src))
if err != nil { if err != nil {
log.WithError(err).Error("Error decoding private key")
panic(err) panic(err)
} }
return dest return dest
@ -143,6 +192,7 @@ func (k I2PKeys) PrivateKey() crypto.PrivateKey {
var pk ed25519.PrivateKey = k.Private() var pk ed25519.PrivateKey = k.Private()
_, err := pk.Sign(rand.Reader, []byte("nonsense"), crypto.Hash(0)) _, err := pk.Sign(rand.Reader, []byte("nonsense"), crypto.Hash(0))
if err != nil { if err != nil {
log.WithError(err).Warn("Error in private key signature")
//TODO: Elgamal, P256, P384, P512, GOST? keys? //TODO: Elgamal, P256, P384, P512, GOST? keys?
} }
return pk return pk
@ -173,7 +223,8 @@ func (k I2PKeys) String() string {
func (k I2PKeys) HostnameEntry(hostname string, opts crypto.SignerOpts) (string, error) { func (k I2PKeys) HostnameEntry(hostname string, opts crypto.SignerOpts) (string, error) {
sig, err := k.Sign(rand.Reader, []byte(hostname), opts) sig, err := k.Sign(rand.Reader, []byte(hostname), opts)
if err != nil { if err != nil {
return "", err log.WithError(err).Error("Error signing hostname")
return "", fmt.Errorf("error signing hostname: %w", err)
} }
return string(sig), nil return string(sig), nil
} }
@ -189,25 +240,33 @@ type I2PDestHash [32]byte
// create a desthash from a string b32.i2p address // create a desthash from a string b32.i2p address
func DestHashFromString(str string) (dhash I2PDestHash, err error) { func DestHashFromString(str string) (dhash I2PDestHash, err error) {
log.WithField("address", str).Debug("Creating desthash from string")
if strings.HasSuffix(str, ".b32.i2p") && len(str) == 60 { if strings.HasSuffix(str, ".b32.i2p") && len(str) == 60 {
// valid // valid
_, err = i2pB32enc.Decode(dhash[:], []byte(str[:52]+"====")) _, err = i2pB32enc.Decode(dhash[:], []byte(str[:52]+"===="))
if err != nil {
log.WithError(err).Error("Error decoding base32 address")
}
} else { } else {
// invalid // invalid
err = errors.New("invalid desthash format") err = errors.New("invalid desthash format")
log.WithError(err).Error("Invalid desthash format")
} }
return return
} }
// create a desthash from a []byte array // create a desthash from a []byte array
func DestHashFromBytes(str []byte) (dhash I2PDestHash, err error) { func DestHashFromBytes(str []byte) (dhash I2PDestHash, err error) {
log.Debug("Creating DestHash from bytes")
if len(str) == 32 { if len(str) == 32 {
// valid // valid
//_, err = i2pB32enc.Decode(dhash[:], []byte(str[:52]+"====")) //_, err = i2pB32enc.Decode(dhash[:], []byte(str[:52]+"===="))
log.WithField("str", str).Debug("Copying str to desthash")
copy(dhash[:], str) copy(dhash[:], str)
} else { } else {
// invalid // invalid
err = errors.New("invalid desthash format") err = errors.New("invalid desthash format")
log.WithField("str", str).Error("Invalid desthash format")
} }
return return
} }
@ -255,10 +314,11 @@ func (a I2PAddr) Network() string {
// Creates a new I2P address from a base64-encoded string. Checks if the address // Creates a new I2P address from a base64-encoded string. Checks if the address
// addr is in correct format. (If you know for sure it is, use I2PAddr(addr).) // addr is in correct format. (If you know for sure it is, use I2PAddr(addr).)
func NewI2PAddrFromString(addr string) (I2PAddr, error) { func NewI2PAddrFromString(addr string) (I2PAddr, error) {
log.WithField("addr", addr).Debug("Creating new I2PAddr from string")
if strings.HasSuffix(addr, ".i2p") { if strings.HasSuffix(addr, ".i2p") {
if strings.HasSuffix(addr, ".b32.i2p") { if strings.HasSuffix(addr, ".b32.i2p") {
// do a lookup of the b32 // do a lookup of the b32
log.Warn("Cannot convert .b32.i2p to full destination")
return I2PAddr(""), errors.New("cannot convert .b32.i2p to full destination") return I2PAddr(""), errors.New("cannot convert .b32.i2p to full destination")
} }
// strip off .i2p if it's there // strip off .i2p if it's there
@ -267,16 +327,20 @@ func NewI2PAddrFromString(addr string) (I2PAddr, error) {
addr = strings.Trim(addr, "\t\n\r\f ") addr = strings.Trim(addr, "\t\n\r\f ")
// very basic check // very basic check
if len(addr) > 4096 || len(addr) < 516 { if len(addr) > 4096 || len(addr) < 516 {
log.Error("Invalid I2P address length")
return I2PAddr(""), errors.New(addr + " is not an I2P address") return I2PAddr(""), errors.New(addr + " is not an I2P address")
} }
buf := make([]byte, i2pB64enc.DecodedLen(len(addr))) buf := make([]byte, i2pB64enc.DecodedLen(len(addr)))
if _, err := i2pB64enc.Decode(buf, []byte(addr)); err != nil { if _, err := i2pB64enc.Decode(buf, []byte(addr)); err != nil {
log.Error("Address is not base64-encoded")
return I2PAddr(""), errors.New("Address is not base64-encoded") return I2PAddr(""), errors.New("Address is not base64-encoded")
} }
log.Debug("Successfully created I2PAddr from string")
return I2PAddr(addr), nil return I2PAddr(addr), nil
} }
func FiveHundredAs() I2PAddr { func FiveHundredAs() I2PAddr {
log.Debug("Generating I2PAddr with 500 'A's")
s := "" s := ""
for x := 0; x < 517; x++ { for x := 0; x < 517; x++ {
s += "A" s += "A"
@ -287,7 +351,9 @@ func FiveHundredAs() I2PAddr {
// Creates a new I2P address from a byte array. The inverse of ToBytes(). // Creates a new I2P address from a byte array. The inverse of ToBytes().
func NewI2PAddrFromBytes(addr []byte) (I2PAddr, error) { func NewI2PAddrFromBytes(addr []byte) (I2PAddr, error) {
log.Debug("Creating I2PAddr from bytes")
if len(addr) > 4096 || len(addr) < 384 { if len(addr) > 4096 || len(addr) < 384 {
log.Error("Invalid I2P address length")
return I2PAddr(""), errors.New("Not an I2P address") return I2PAddr(""), errors.New("Not an I2P address")
} }
buf := make([]byte, i2pB64enc.EncodedLen(len(addr))) buf := make([]byte, i2pB64enc.EncodedLen(len(addr)))
@ -335,6 +401,11 @@ HELLO VERSION MIN=3.1 MAX=3.1
DEST GENERATE SIGNATURE_TYPE=7 DEST GENERATE SIGNATURE_TYPE=7
*/ */
func NewDestination() (*I2PKeys, error) { func NewDestination() (*I2PKeys, error) {
removeNewlines := func(s string) string {
return strings.ReplaceAll(strings.ReplaceAll(s, "\r\n", ""), "\n", "")
}
//
log.Debug("Creating new destination via SAM")
conn, err := net.Dial("tcp", "127.0.0.1:7656") conn, err := net.Dial("tcp", "127.0.0.1:7656")
if err != nil { if err != nil {
return nil, err return nil, err
@ -342,30 +413,49 @@ func NewDestination() (*I2PKeys, error) {
defer conn.Close() defer conn.Close()
_, err = conn.Write([]byte("HELLO VERSION MIN=3.1 MAX=3.1\n")) _, err = conn.Write([]byte("HELLO VERSION MIN=3.1 MAX=3.1\n"))
if err != nil { if err != nil {
log.WithError(err).Error("Error writing to SAM bridge")
return nil, err return nil, err
} }
buf := make([]byte, 4096) buf := make([]byte, 4096)
n, err := conn.Read(buf) n, err := conn.Read(buf)
if err != nil { if err != nil {
log.WithError(err).Error("Error reading from SAM bridge")
return nil, err return nil, err
} }
if n < 1 { if n < 1 {
log.Error("No data received from SAM bridge")
return nil, fmt.Errorf("no data received") return nil, fmt.Errorf("no data received")
} }
response := string(buf[:n])
log.WithField("response", response).Debug("Received response from SAM bridge")
if strings.Contains(string(buf[:n]), "RESULT=OK") { if strings.Contains(string(buf[:n]), "RESULT=OK") {
_, err = conn.Write([]byte("DEST GENERATE SIGNATURE_TYPE=7\n")) _, err = conn.Write([]byte("DEST GENERATE SIGNATURE_TYPE=7\n"))
if err != nil { if err != nil {
log.WithError(err).Error("Error writing DEST GENERATE to SAM bridge")
return nil, err return nil, err
} }
n, err = conn.Read(buf) n, err = conn.Read(buf)
if err != nil { if err != nil {
log.WithError(err).Error("Error reading destination from SAM bridge")
return nil, err return nil, err
} }
if n < 1 { if n < 1 {
log.Error("No destination data received from SAM bridge")
return nil, fmt.Errorf("no destination data received") return nil, fmt.Errorf("no destination data received")
} }
pub := strings.Split(strings.Split(string(buf[:n]), "PRIV=")[0], "PUB=")[1] pub := strings.Split(strings.Split(string(buf[:n]), "PRIV=")[0], "PUB=")[1]
priv := strings.Split(string(buf[:n]), "PRIV=")[1] _priv := strings.Split(string(buf[:n]), "PRIV=")[1]
priv := removeNewlines(_priv) //There is an extraneous newline in the private key, so we'll remove it.
log.WithFields(logrus.Fields{
"_priv(pre-newline removal)": _priv,
"priv": priv,
}).Debug("Removed newline")
log.Debug("Successfully created new destination")
return &I2PKeys{ return &I2PKeys{
Address: I2PAddr(pub), Address: I2PAddr(pub),
@ -373,5 +463,6 @@ func NewDestination() (*I2PKeys, error) {
}, nil }, nil
} }
log.Error("No RESULT=OK received from SAM bridge")
return nil, fmt.Errorf("no result received") return nil, fmt.Errorf("no result received")
} }

View File

@ -1,21 +1,28 @@
package i2pkeys package i2pkeys
import ( import (
"bytes"
"fmt" "fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing" "testing"
// "time"
) )
const yoursam = "127.0.0.1:7656" const (
yoursam = "127.0.0.1:7656"
validShortenedI2PAddr = "idk.i2p"
validI2PAddrB32 = "b2o47zwxqjbn7jj37yqkmvbmci7kqubwgxu3umqid7cexmc7xudq.b32.i2p"
validI2PAddrB64 = "spHxea2xhPjKH9yyEeFJ96aqtvKidH-GiWxs8dH6RWS2FrDoWFhuEkfw77pF~Hv57lLhMaMB3qqWjCtYXOjL48Q1zYbr3MAcTO44wwVPjOU1hU77vbJcUuwBeRvaSr2dZx-FiTSOdQuhPD1EozYNRIMFwZ0fZwKf~3Gj4dEWccOLKs~NbiPsj-~tc5tmhAs8yBeoZEqEBe40X75SfSHY-EnstcZevVAwIXYk3zX3KF0mji3bo2QXuTFcMZHHLiLd2AHLRANzWyvQ9DC1rnCsHJM4xxV4dVp0pHkP1hwBo7E0NJvN4nFkQcj-FI2RJ~cFUCk7qc86PRHwvKCjzSlrgjtDsMUwd83Dz1PfpzCqHNLUFWI7uPKbKcJZhasFm4kEhUyupd85q75Ch2IZE9J2JXodSxmseO5ZKcHK6pFtfR-HbzKjIe92TWHsNkmvtoHiUaOVrWnk-cmo2I1W1VxfL08teDxQ13P80uFaMcameRzuFM2F8pSOpoyEJUDRGLEeBQAEAAcAAA=="
)
func Test_Basic(t *testing.T) { func Test_Basic(t *testing.T) {
fmt.Println("Test_Basic") fmt.Println("Test_Basic")
fmt.Println("\tAttaching to SAM at " + yoursam) fmt.Println("\tAttaching to SAM at " + yoursam)
keys, err := NewDestination() keys, err := NewDestination()
if err != nil { if err != nil {
fmt.Println(err.Error()) t.Fatal(err.Error())
t.Fail()
return
} }
fmt.Println(keys.String()) fmt.Println(keys.String())
} }
@ -23,11 +30,299 @@ func Test_Basic(t *testing.T) {
func Test_Basic_Lookup(t *testing.T) { func Test_Basic_Lookup(t *testing.T) {
fmt.Println("Test_Basic") fmt.Println("Test_Basic")
fmt.Println("\tAttaching to SAM at " + yoursam) fmt.Println("\tAttaching to SAM at " + yoursam)
keys, err := Lookup("idk.i2p") keys, err := Lookup(validShortenedI2PAddr)
if err != nil { if err != nil {
fmt.Println(err.Error()) t.Fatal(err.Error())
t.Fail()
return
} }
fmt.Println(keys.String()) fmt.Println(keys.String())
} }
func Test_NewI2PAddrFromString(t *testing.T) {
t.Run("Valid base64 address", func(t *testing.T) {
addr, err := NewI2PAddrFromString(validI2PAddrB64)
if err != nil {
t.Fatalf("NewI2PAddrFromString failed for valid address: '%v'", err)
}
if addr.Base64() != validI2PAddrB64 {
t.Errorf("NewI2PAddrFromString returned incorrect address. Got '%s', want '%s'", addr.Base64(), validI2PAddrB64)
}
})
t.Run("Invalid address", func(t *testing.T) {
invalidAddr := "not-a-valid-address"
_, err := NewI2PAddrFromString(invalidAddr)
if err == nil {
t.Error("NewI2PAddrFromString should have failed for invalid address")
}
})
t.Run("Base32 address", func(t *testing.T) {
_, err := NewI2PAddrFromString(validI2PAddrB32)
if err == nil {
t.Error("NewI2PAddrFromString should have failed for base32 address")
}
})
t.Run("Empty address", func(t *testing.T) {
_, err := NewI2PAddrFromString("")
if err == nil {
t.Error("NewI2PAddrFromString should have failed for empty address")
}
})
t.Run("Address with .i2p suffix", func(t *testing.T) { //CHECK
addr, err := NewI2PAddrFromString(validI2PAddrB64 + ".i2p")
if err != nil {
t.Fatalf("NewI2PAddrFromString failed for address with .i2p suffix: '%v'", err)
}
if addr.Base64() != validI2PAddrB64 {
t.Errorf("NewI2PAddrFromString returned incorrect address. Got '%s', want '%s'", addr.Base64(), validI2PAddrB64)
}
})
}
func Test_I2PAddr(t *testing.T) {
addr := I2PAddr(validI2PAddrB64)
base32 := addr.Base32()
t.Run("Base32 suffix", func(t *testing.T) {
if !strings.HasSuffix(base32, ".b32.i2p") {
t.Errorf("Base32 address should end with .b32.i2p, got %s", base32)
}
})
t.Run("Base32 length", func(t *testing.T) {
if len(base32) != 60 {
t.Errorf("Base32 address should be 60 characters long, got %d", len(base32))
}
})
}
func Test_DestHashFromString(t *testing.T) {
t.Run("Valid hash", func(t *testing.T) {
hash, err := DestHashFromString(validI2PAddrB32)
if err != nil {
t.Fatalf("DestHashFromString failed for valid hash: '%v'", err)
}
if hash.String() != validI2PAddrB32 {
t.Errorf("DestHashFromString returned incorrect hash. Got '%s', want '%s'", hash.String(), validI2PAddrB32)
}
})
t.Run("Invalid hash", func(t *testing.T) {
invalidHash := "not-a-valid-hash"
_, err := DestHashFromString(invalidHash)
if err == nil {
t.Error("DestHashFromString should have failed for invalid hash")
}
})
t.Run("Empty hash", func(t *testing.T) {
_, err := DestHashFromString("")
if err == nil {
t.Error("DestHashFromString should have failed for empty hash")
}
})
}
func Test_I2PAddrToBytes(t *testing.T) {
addr := I2PAddr(validI2PAddrB64)
t.Run("ToBytes and back", func(t *testing.T) {
decodedBytes, err := addr.ToBytes()
if err != nil {
t.Fatalf("ToBytes failed: '%v'", err)
}
encodedString := i2pB64enc.EncodeToString(decodedBytes)
if encodedString != validI2PAddrB64 {
t.Errorf("Round-trip encoding/decoding failed. Got '%s', want '%s'", encodedString, validI2PAddrB64)
}
})
t.Run("Direct decoding comparison", func(t *testing.T) {
decodedBytes, err := addr.ToBytes()
if err != nil {
t.Fatalf("ToBytes failed: '%v'", err)
}
directlyDecoded, err := i2pB64enc.DecodeString(validI2PAddrB64)
if err != nil {
t.Fatalf("Failed to decode test string using i2pB64enc: '%v'", err)
}
if !bytes.Equal(decodedBytes, directlyDecoded) {
t.Errorf("Mismatch between ToBytes result and direct decoding. ToBytes len: '%d', Direct decoding len: '%d'", len(decodedBytes), len(directlyDecoded))
}
})
}
/*
func removeNewlines(s string) string {
return strings.ReplaceAll(strings.ReplaceAll(s, "\r\n", ""), "\n", "")
}
*/
func Test_KeyGenerationAndHandling(t *testing.T) {
// Generate new keys
keys, err := NewDestination()
if err != nil {
t.Fatalf("Failed to generate new I2P keys: %v", err)
}
t.Run("LoadKeysIncompat", func(t *testing.T) {
//extract keys
addr := keys.Address
fmt.Println(addr)
//both := removeNewlines(keys.Both)
both := keys.Both
fmt.Println(both)
//FORMAT TO LOAD: (Address, Both)
addrload := addr.Base64() + "\n" + both
r := strings.NewReader(addrload)
loadedKeys, err := LoadKeysIncompat(r)
if err != nil {
t.Fatalf("LoadKeysIncompat failed: %v", err)
}
if loadedKeys.Address != keys.Address {
//fmt.Printf("loadedKeys.Address md5hash: '%s'\n keys.Address md5hash: '%s'\n", getMD5Hash(string(loadedKeys.Address)), getMD5Hash(string(keys.Address)))
t.Errorf("LoadKeysIncompat returned incorrect address. Got '%s', want '%s'", loadedKeys.Address, keys.Address)
}
if loadedKeys.Both != keys.Both {
t.Errorf("LoadKeysIncompat returned incorrect pair. Got '%s'\nwant '%s'\n", loadedKeys.Both, keys.Both)
/*
if loadedKeys.Both == removeNewlines(keys.Both) {
fmt.Println("However, both pairs are correct if newline is removed in generated keys.")
}
*/
}
})
expected := keys.Address.Base64() + "\n" + keys.Both
t.Run("StoreKeysIncompat", func(t *testing.T) {
var buf bytes.Buffer
err := StoreKeysIncompat(*keys, &buf)
if err != nil {
t.Fatalf("StoreKeysIncompat failed: '%v'", err)
}
if buf.String() != expected {
t.Errorf("StoreKeysIncompat wrote incorrect data. Got '%s', want '%s'", buf.String(), expected)
}
})
t.Run("StoreKeys", func(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "test_keys_")
if err != nil {
t.Fatalf("Failed to create temp directory: '%v'", err)
}
defer os.RemoveAll(tmpDir)
tmpFilePath := filepath.Join(tmpDir, "test_keys.txt")
err = StoreKeys(*keys, tmpFilePath)
if err != nil {
t.Fatalf("StoreKeys failed: '%v'", err)
}
content, err := ioutil.ReadFile(tmpFilePath)
if err != nil {
t.Fatalf("Failed to read temp file: '%v'", err)
}
if string(content) != expected {
t.Errorf("StoreKeys wrote incorrect data. Got '%s', want '%s'", string(content), expected)
}
})
}
func Test_KeyStorageAndLoading(t *testing.T) {
// Generate initial keys
keys, err := NewDestination()
if err != nil {
t.Fatalf("Failed to generate new I2P keys: %v", err)
}
t.Run("StoreAndLoadFile", func(t *testing.T) {
// Create temporary directory for test
tmpDir, err := ioutil.TempDir("", "test_keys_")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
defer os.RemoveAll(tmpDir)
tmpFilePath := filepath.Join(tmpDir, "test_keys.txt")
// Store keys to file
err = StoreKeys(*keys, tmpFilePath)
if err != nil {
t.Fatalf("StoreKeys failed: %v", err)
}
// Load keys from file
loadedKeys, err := LoadKeys(tmpFilePath)
if err != nil {
t.Fatalf("LoadKeys failed: %v", err)
}
// Verify loaded keys match original
if loadedKeys.Address != keys.Address {
t.Errorf("Loaded address does not match original. Got %s, want %s",
loadedKeys.Address, keys.Address)
}
if loadedKeys.Both != keys.Both {
t.Errorf("Loaded keypair does not match original. Got %s, want %s",
loadedKeys.Both, keys.Both)
}
})
t.Run("StoreAndLoadIncompat", func(t *testing.T) {
var buf bytes.Buffer
// Store keys to buffer
err := StoreKeysIncompat(*keys, &buf)
if err != nil {
t.Fatalf("StoreKeysIncompat failed: %v", err)
}
// Create new reader from buffer content
reader := strings.NewReader(buf.String())
// Load keys from reader
loadedKeys, err := LoadKeysIncompat(reader)
if err != nil {
t.Fatalf("LoadKeysIncompat failed: %v", err)
}
// Verify loaded keys match original
if loadedKeys.Address != keys.Address {
t.Errorf("Loaded address does not match original. Got %s, want %s",
loadedKeys.Address, keys.Address)
}
if loadedKeys.Both != keys.Both {
t.Errorf("Loaded keypair does not match original. Got %s, want %s",
loadedKeys.Both, keys.Both)
}
})
t.Run("LoadNonexistentFile", func(t *testing.T) {
nonexistentPath := filepath.Join(os.TempDir(), "nonexistent_keys.txt")
_, err := LoadKeys(nonexistentPath)
if err != os.ErrNotExist {
t.Errorf("Expected ErrNotExist for nonexistent file, got: %v", err)
}
})
}
func Test_BasicInvalidAddress(t *testing.T) {
invalidAddr := strings.Repeat("x", 60)
invalidAddr += ".b32.i2p"
_, err := Lookup(invalidAddr)
if err == nil {
t.Fatal("Expected error for nonexistent address")
}
}

View File

@ -7,41 +7,60 @@ import (
) )
func Lookup(addr string) (*I2PAddr, error) { func Lookup(addr string) (*I2PAddr, error) {
log.WithField("addr", addr).Debug("Starting Lookup")
conn, err := net.Dial("tcp", "127.0.0.1:7656") conn, err := net.Dial("tcp", "127.0.0.1:7656")
if err != nil { if err != nil {
log.Error("Failed to connect to SAM bridge")
return nil, err return nil, err
} }
defer conn.Close() defer conn.Close()
_, err = conn.Write([]byte("HELLO VERSION MIN=3.1 MAX=3.1\n")) _, err = conn.Write([]byte("HELLO VERSION MIN=3.1 MAX=3.1\n"))
if err != nil { if err != nil {
log.Error("Failed to write HELLO VERSION")
return nil, err return nil, err
} }
buf := make([]byte, 4096) buf := make([]byte, 4096)
n, err := conn.Read(buf) n, err := conn.Read(buf)
if err != nil { if err != nil {
log.Error("Failed to read HELLO VERSION response")
return nil, err return nil, err
} }
if n < 1 { if n < 1 {
log.Error("no data received")
return nil, fmt.Errorf("no data received") return nil, fmt.Errorf("no data received")
} }
response := string(buf[:n])
log.WithField("response", response).Debug("Received HELLO response")
if strings.Contains(string(buf[:n]), "RESULT=OK") { if strings.Contains(string(buf[:n]), "RESULT=OK") {
_, err = conn.Write([]byte(fmt.Sprintf("NAMING LOOKUP NAME=%s\n", addr))) _, err = conn.Write([]byte(fmt.Sprintf("NAMING LOOKUP NAME=%s\n", addr)))
if err != nil { if err != nil {
log.Error("Failed to write NAMING LOOKUP command")
return nil, err return nil, err
} }
n, err = conn.Read(buf) n, err = conn.Read(buf)
if err != nil { if err != nil {
log.Error("Failed to read NAMING LOOKUP response")
return nil, err return nil, err
} }
if n < 1 { if n < 1 {
return nil, fmt.Errorf("no destination data received") return nil, fmt.Errorf("no destination data received")
} }
value := strings.Split(string(buf[:n]), "VALUE=")[1] parts := strings.Split(string(buf[:n]), "VALUE=")
if len(parts) < 2 {
log.Error("Could not find VALUE=, maybe we couldn't find the destination?")
return nil, fmt.Errorf("could not find VALUE=")
}
value := parts[1]
addr, err := NewI2PAddrFromString(value) addr, err := NewI2PAddrFromString(value)
if err != nil { if err != nil {
log.Error("Failed to parse I2P address from lookup response")
return nil, err return nil, err
} }
log.WithField("addr", addr).Debug("Successfully resolved I2P address")
return &addr, err return &addr, err
} }
log.Error("no RESULT=OK received in HELLO response")
return nil, fmt.Errorf("no result received") return nil, fmt.Errorf("no result received")
} }

View File

@ -30,3 +30,93 @@ fmt:
upload-linux: upload-linux:
github-release upload -R -u $(USER_GH) -r "$(packagename)" -t $(VERSION) -l `sha256sum ` -n "$(packagename)" -f "$(packagename)" github-release upload -R -u $(USER_GH) -r "$(packagename)" -t $(VERSION) -l `sha256sum ` -n "$(packagename)" -f "$(packagename)"
test-basic:
go test -v -run Test_Basic
test-basic-lookup:
go test -v -run Test_Basic_Lookup
test-newi2paddrfromstring:
go test -v -run Test_NewI2PAddrFromString
test-i2paddr:
go test -v -run Test_I2PAddr
test-desthashfromstring:
go test -v -run Test_DestHashFromString
test-i2paddr-to-bytes:
go test -v -run Test_I2PAddrToBytes
test-key-generation-and-handling:
go test -v -run Test_KeyGenerationAndHandling
# Subtest targets
test-newi2paddrfromstring-valid:
go test -v -run Test_NewI2PAddrFromString/Valid_base64_address
test-newi2paddrfromstring-invalid:
go test -v -run Test_NewI2PAddrFromString/Invalid_address
test-newi2paddrfromstring-base32:
go test -v -run Test_NewI2PAddrFromString/Base32_address
test-newi2paddrfromstring-empty:
go test -v -run Test_NewI2PAddrFromString/Empty_address
test-newi2paddrfromstring-i2p-suffix:
go test -v -run Test_NewI2PAddrFromString/Address_with_.i2p_suffix
test-i2paddr-base32-suffix:
go test -v -run Test_I2PAddr/Base32_suffix
test-i2paddr-base32-length:
go test -v -run Test_I2PAddr/Base32_length
test-desthashfromstring-valid:
go test -v -run Test_DestHashFromString/Valid_hash
test-desthashfromstring-invalid:
go test -v -run Test_DestHashFromString/Invalid_hash
test-desthashfromstring-empty:
go test -v -run Test_DestHashFromString/Empty_hash
test-i2paddr-to-bytes-roundtrip:
go test -v -run Test_I2PAddrToBytes/ToBytes_and_back
test-i2paddr-to-bytes-comparison:
go test -v -run Test_I2PAddrToBytes/Direct_decoding_comparison
test-key-generation-and-handling-loadkeys:
go test -v -run Test_KeyGenerationAndHandling/LoadKeysIncompat
test-key-generation-and-handling-storekeys-incompat:
go test -v -run Test_KeyGenerationAndHandling/StoreKeysIncompat
test-key-generation-and-handling-storekeys:
go test -v -run Test_KeyGenerationAndHandling/StoreKeys
test-key-storage:
go test -v -run Test_KeyStorageAndLoading
# Individual key storage subtests
test-key-storage-file:
go test -v -run Test_KeyStorageAndLoading/StoreAndLoadFile
test-key-storage-incompat:
go test -v -run Test_KeyStorageAndLoading/StoreAndLoadIncompat
test-key-storage-nonexistent:
go test -v -run Test_KeyStorageAndLoading/LoadNonexistentFile
test-basic-invalid-address:
go test -v -run Test_BasicInvalidAddress
# Aggregate targets
test-all:
go test -v ./...
test-subtests: test-newi2paddrfromstring-valid test-newi2paddrfromstring-invalid test-newi2paddrfromstring-base32 test-newi2paddrfromstring-empty test-newi2paddrfromstring-i2p-suffix test-i2paddr-base32-suffix test-i2paddr-base32-length test-desthashfromstring-valid test-desthashfromstring-invalid test-desthashfromstring-empty test-i2paddr-to-bytes-roundtrip test-i2paddr-to-bytes-comparison test-key-generation-and-handling-loadkeys test-key-generation-and-handling-storekeys-incompat test-key-generation-and-handling-storekeys test-key-storage-file test-key-storage-incompat test-key-storage-nonexistent
test: test-basic test-basic-lookup test-newi2paddrfromstring test-i2paddr test-desthashfromstring test-i2paddr-to-bytes test-key-generation-and-handling test-key-storage test-basic-invalid-address test-subtests test-all

View File

@ -3,3 +3,23 @@ i2pkeys
Generates and displays the contents of files that are storing i2p keys in the Generates and displays the contents of files that are storing i2p keys in the
incompatible format used for sam3 incompatible format used for sam3
## Verbosity ##
Logging can be enabled and configured using the DEBUG_I2P environment variable. By default, logging is disabled.
There are three available log levels:
- Debug
```shell
export DEBUG_I2P=debug
```
- Warn
```shell
export DEBUG_I2P=warn
```
- Error
```shell
export DEBUG_I2P=error
```
If DEBUG_I2P is set to an unrecognized variable, it will fall back to "debug".

5
go.mod
View File

@ -1,3 +1,8 @@
module github.com/go-i2p/i2pkeys module github.com/go-i2p/i2pkeys
go 1.17 go 1.17
require (
github.com/sirupsen/logrus v1.9.3 // indirect
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
)

50
log.go Normal file
View File

@ -0,0 +1,50 @@
package i2pkeys
import (
"github.com/sirupsen/logrus"
"io/ioutil"
"os"
"strings"
"sync"
)
var (
log *logrus.Logger
once sync.Once
)
func InitializeI2PKeysLogger() {
once.Do(func() {
log = logrus.New()
// We do not want to log by default
log.SetOutput(ioutil.Discard)
log.SetLevel(logrus.PanicLevel)
// Check if DEBUG_I2P is set
if logLevel := os.Getenv("DEBUG_I2P"); logLevel != "" {
log.SetOutput(os.Stdout)
switch strings.ToLower(logLevel) {
case "debug":
log.SetLevel(logrus.DebugLevel)
case "warn":
log.SetLevel(logrus.WarnLevel)
case "error":
log.SetLevel(logrus.ErrorLevel)
default:
log.SetLevel(logrus.DebugLevel)
}
log.WithField("level", log.GetLevel()).Debug("Logging enabled.")
}
})
}
// GetI2PKeysLogger returns the initialized logger
func GetI2PKeysLogger() *logrus.Logger {
if log == nil {
InitializeI2PKeysLogger()
}
return log
}
func init() {
InitializeI2PKeysLogger()
}