55 Commits

Author SHA1 Message Date
idk
edace64685 Merge pull request #12 from hkh4n/refactor
return error instead of panic when VALUE= is not found
2024-11-07 17:06:48 +00:00
Haris Khan
1712957252 add unit test 2024-11-07 11:39:04 -05:00
Haris Khan
eb51810ddd minor grammar tweaks 2024-11-07 11:16:05 -05:00
Haris Khan
c4cbd6e041 return error instead of panic when VALUE= is not found 2024-11-07 11:07:13 -05:00
idk
84d1ec1a12 Merge pull request #9 from hkh4n/tests
added Test_KeyStorageAndLoading
2024-11-04 05:01:36 +00:00
idk
43fb66564b Merge pull request #10 from hkh4n/refactor
Refactored LoadKeysIncompat & Finished up LoadKeys()
2024-11-03 22:19:33 +00:00
Haris Khan
2e82fab112 update Makefile 2024-11-03 12:01:55 -05:00
Haris Khan
daa08faa71 Minor typo fix 2024-11-02 19:42:22 -04:00
Haris Khan
a05ca99118 Merge branch 'loadkeys' into refactor 2024-10-26 22:59:30 -04:00
Haris Khan
b99e77153e Make LoadKeys() create new keys if the keyfile doesn't exist 2024-10-26 22:59:06 -04:00
Haris Khan
b56afeb346 tweaks 2024-10-26 19:20:37 -04:00
Haris Khan
890d71f974 comments 2024-10-26 19:20:05 -04:00
Haris Khan
bfcde005b3 Refactored LoadKeysIncompat
-Previously: would log errors regardless at the end
-Better error handling
2024-10-26 19:18:08 -04:00
Haris Khan
a9a4310a04 added Test_KeyStorageAndLoading 2024-10-26 18:37:33 -04:00
idk
4cba6e2edd Merge pull request #8 from hkh4n/refactor
return os.ErrNotExist instead of written error
2024-10-26 21:35:44 +00:00
Haris Khan
25eedfeed8 return os.ErrNotExist instead of written error 2024-10-26 13:50:59 -04:00
idk
8632c8275d Merge pull request #7 from hkh4n/logging
logging naming convention hotfix
2024-10-23 17:34:31 +00:00
Haris Khan
9e61a8e00f Merge branch 'eyedeekay:master' into logging 2024-10-23 00:10:22 -04:00
Haris Khan
fb2ca1a92c fix logger naming collision with other libs 2024-10-23 00:09:36 -04:00
Haris Khan
1bf0437e54 tweaks 2024-10-17 14:52:00 -04:00
idk
0af72cba75 Merge pull request #6 from hkh4n/logging
Added logging to Lookup.go and I2PAddr.go
2024-10-17 18:51:39 +00:00
Haris Khan
a4a38460e3 tweaks 2024-10-17 14:42:38 -04:00
Haris Khan
75a7b2aec9 Update README.md to reflect logging 2024-10-17 14:38:35 -04:00
Haris Khan
43a6a0e07f adjusted log.go 2024-10-16 14:59:01 -04:00
Haris Khan
936c39746c tweaks 2024-10-15 19:48:13 -04:00
Haris Khan
8df0f31a4d Added logging to I2PAddr.go
-removed "trace" and "info"
-moved log to its own file
-shifted priv -> _priv (pre newline removal)
-added response string in Lookup()
-added response string in NewDestination()
2024-10-15 09:53:10 -04:00
Haris Khan
c1b05d6ede !WIP! - added logging 2024-10-14 23:28:25 -04:00
idk
0ef26b9207 Merge pull request #5 from hkh4n/makefile
Updated Makefile
2024-09-24 13:10:37 -04:00
Haris Khan
8eee571a7b -remove boilerplate artifact 2024-09-22 20:04:55 -04:00
Haris Khan
95744f9498 -bump version
-added testing from make
2024-09-22 19:52:02 -04:00
idk
b4e9da89e9 Merge pull request #3 from hkh4n/tests
Added tests for addresses and keys. Changed NewDestination()
2024-09-17 18:18:41 -04:00
Haris Khan
afae8e6f14 changed urls to fit with tests 2024-09-15 21:56:23 -04:00
Haris Khan
6c95fc6ac7 -Critical change: trim newline from private key
-commented out newline investigation in Test_KeyGenerationAndHandling
-Test_KeyGnerationAndHandling works as expected now.
2024-09-15 21:48:51 -04:00
Haris Khan
ada0d39af4 Investigating LoadKeysIncompat (amend instead) 2024-09-15 15:17:33 -04:00
Haris Khan
59bffea3f3 Merge remote-tracking branch 'origin/tests-keys' into tests-keys
# Conflicts:
#	I2PAddr_test.go
2024-09-15 13:13:36 -04:00
Haris Khan
316dc840d6 Investigating LoadKeysIncompat (amend instead) 2024-09-15 13:08:11 -04:00
Haris Khan
90da121025 Investigating LoadKeysIncompat x4 2024-09-15 12:31:39 -04:00
Haris Khan
d362997650 Investigating LoadKeysIncompat x3 2024-09-15 11:28:47 -04:00
Haris Khan
4004d3050d Investigating LoadKeysIncompat x2 2024-09-15 11:16:46 -04:00
Haris Khan
0d58ebfa78 Investigating LoadKeysIncompat 2024-09-15 11:15:18 -04:00
Haris Khan
acbf68bc58 Added broken tests for keys (TODO) 2024-09-15 00:46:32 -04:00
Haris Khan
dc8ef52d46 Revamped failures for Test_basic() and Test_Basic_Lookup().
Added:
-Test_NewI2PAddrFromString()
-Test_I2PAddr()
-Test_DestHashFromString()
-Test_I2PAddrToBytes()

with sub-tests.
2024-09-15 00:45:09 -04:00
Haris Khan
aae46b4dec added TestKeyGenerationAndHandling 2024-09-14 21:31:52 -04:00
Haris Khan
597e1da68d Merge remote-tracking branch 'origin/tests' into tests + Correct Func names as "Test_"
# Conflicts:
#	I2PAddr_test.go
2024-09-14 08:04:19 -04:00
Haris Khan
4cf76aeec2 Added various tests 2024-09-14 07:59:56 -04:00
Haris Khan
80bfc77145 Added various tests 2024-09-14 00:32:23 -04:00
idk
5ae94bc639 Merge pull request #1 from hkh4n/error-handling
More robust error handling in I2PAddr.go
2024-09-10 14:33:24 -04:00
Haris Khan
c86c07c1df refactored error handling in HostnameEntry() 2024-09-09 20:20:43 -04:00
Haris Khan
fde718e1d8 refactored error handling in StoreKeysIncompat() 2024-09-09 20:17:21 -04:00
Haris Khan
3a99966c42 refactored error handling in LoadKeys() 2024-09-09 20:07:43 -04:00
Haris Khan
d23cb52f2c refactored error handling in fileExists() 2024-09-09 20:02:56 -04:00
eyedeekay
81f9b8a8cc update makefile 2024-01-09 14:43:01 -05:00
idk
28d6bf4d97 update go modules 2023-03-07 02:11:16 +00:00
idk
9307ae9cf4 StringIsBase64 is bittorrent mode 2022-09-22 23:14:43 -04:00
idk
e6cb984e8f add option to only return b64s 2022-09-21 00:36:57 -04:00
7 changed files with 630 additions and 36 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"
@@ -21,6 +22,9 @@ var (
i2pB32enc *base32.Encoding = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567") i2pB32enc *base32.Encoding = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567")
) )
// If you set this to true, Addr will return a base64 String()
var StringIsBase64 bool
// The public and private keys associated with an I2P destination. I2P hides the // The public and private keys associated with an I2P destination. I2P hides the
// details of exactly what this is, so treat them as blobs, but generally: One // details of exactly what this is, so treat them as blobs, but generally: One
// pair of DSA keys, one pair of ElGamal keys, and sometimes (almost never) also // pair of DSA keys, one pair of ElGamal keys, and sometimes (almost never) also
@@ -34,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}
} }
@@ -42,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 {
fi, err := os.Open(r) // 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 { if err != nil {
log.WithError(err).Error("Error generating new keys")
return I2PKeys{}, err return I2PKeys{}, err
} }
defer fi.Close() // Save the new keys to the file
return LoadKeysIncompat(fi) err = StoreKeys(*k, r)
if err != nil {
log.WithError(err).Error("Error saving new keys to file")
return I2PKeys{}, err
}
return *k, nil
} }
return I2PKeys{}, err fi, err := os.Open(r)
if err != nil {
log.WithError(err).WithField("filename", r).Error("Error opening file")
return I2PKeys{}, fmt.Errorf("error opening file: %w", err)
}
defer fi.Close()
log.WithField("filename", r).Debug("File opened successfully")
return LoadKeysIncompat(fi)
} }
// 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()
@@ -98,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()
@@ -118,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
@@ -140,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
@@ -170,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
} }
@@ -186,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
} }
@@ -238,6 +300,9 @@ func (a I2PAddr) Base64() string {
// Returns the I2P destination (base32-encoded) // Returns the I2P destination (base32-encoded)
func (a I2PAddr) String() string { func (a I2PAddr) String() string {
if StringIsBase64 {
return a.Base64()
}
return string(a.Base32()) return string(a.Base32())
} }
@@ -249,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
@@ -261,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 {
return I2PAddr(""), errors.New("Not an I2P address") log.Error("Invalid I2P address length")
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"
@@ -281,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)))
@@ -329,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
@@ -336,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),
@@ -367,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")
} }

118
Makefile
View File

@@ -1,13 +1,121 @@
build: USER_GH=eyedeekay
go build -a -tags netgo -ldflags '-w -extldflags "-static"' VERSION=0.33.8
packagename=i2pkeys
echo: echo:
@echo "$(GOPATH)" @echo "type make version to do release $(VERSION)"
version:
github-release release -s $(GITHUB_TOKEN) -u $(USER_GH) -r $(packagename) -t v$(VERSION) -d "version $(VERSION)"
del:
github-release delete -s $(GITHUB_TOKEN) -u $(USER_GH) -r $(packagename) -t v$(VERSION)
tar:
tar --exclude .git \
--exclude .go \
--exclude bin \
-cJvf ../$(packagename)_$(VERSION).orig.tar.xz .
copier:
echo '#! /usr/bin/env sh' > deb/copy.sh
echo 'for f in $$(ls); do scp $$f/*.deb user@192.168.99.106:~/DEBIAN_PKGS/$$f/main/; done' >> deb/copy.sh
fmt: fmt:
find . -path ./.go -prune -o -name "*.go" -exec gofmt -w {} \; find . -path ./.go -prune -o -name "*.go" -exec gofmt -w {} \;
find . -path ./.go -prune -o -name "*.i2pkeys" -exec rm {} \; find . -path ./.go -prune -o -name "*.i2pkeys" -exec rm {} \;
install: upload-linux:
install -m755 i2pkeys /usr/local/bin/i2pkeys 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/eyedeekay/i2pkeys module github.com/eyedeekay/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()
}