mirror of
https://github.com/go-i2p/i2pkeys.git
synced 2025-06-08 09:16:22 -04:00
Separate out generate address function and make it's SAM port configurable
This commit is contained in:
128
I2PAddr.go
128
I2PAddr.go
@ -7,13 +7,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Address length constraints
|
// Address length constraints
|
||||||
MinAddressLength = 516
|
MinAddressLength = 516
|
||||||
MaxAddressLength = 4096
|
MaxAddressLength = 4096
|
||||||
|
|
||||||
// Domain suffixes
|
// Domain suffixes
|
||||||
I2PDomainSuffix = ".i2p"
|
I2PDomainSuffix = ".i2p"
|
||||||
B32DomainSuffix = ".b32.i2p"
|
B32DomainSuffix = ".b32.i2p"
|
||||||
)
|
)
|
||||||
|
|
||||||
// I2PAddr represents an I2P destination, equivalent to an IP address.
|
// I2PAddr represents an I2P destination, equivalent to an IP address.
|
||||||
@ -22,98 +22,98 @@ type I2PAddr string
|
|||||||
|
|
||||||
// Base64 returns the raw base64 representation of the I2P address.
|
// Base64 returns the raw base64 representation of the I2P address.
|
||||||
func (a I2PAddr) Base64() string {
|
func (a I2PAddr) Base64() string {
|
||||||
return string(a)
|
return string(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns either the base64 or base32 representation based on configuration.
|
// String returns either the base64 or base32 representation based on configuration.
|
||||||
func (a I2PAddr) String() string {
|
func (a I2PAddr) String() string {
|
||||||
if StringIsBase64 {
|
if StringIsBase64 {
|
||||||
return a.Base64()
|
return a.Base64()
|
||||||
}
|
}
|
||||||
return a.Base32()
|
return a.Base32()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Network returns the network type, always "I2P".
|
// Network returns the network type, always "I2P".
|
||||||
func (a I2PAddr) Network() string {
|
func (a I2PAddr) Network() string {
|
||||||
return "I2P"
|
return "I2P"
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewI2PAddrFromString creates a new I2P address from a base64-encoded string.
|
// NewI2PAddrFromString creates a new I2P address from a base64-encoded string.
|
||||||
// It validates the format and returns an error if the address is invalid.
|
// It validates the format and returns an error if the address is invalid.
|
||||||
func NewI2PAddrFromString(addr string) (I2PAddr, error) {
|
func NewI2PAddrFromString(addr string) (I2PAddr, error) {
|
||||||
addr = sanitizeAddress(addr)
|
addr = sanitizeAddress(addr)
|
||||||
|
|
||||||
if err := validateAddressFormat(addr); err != nil {
|
if err := validateAddressFormat(addr); err != nil {
|
||||||
return I2PAddr(""), err
|
return I2PAddr(""), err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validateBase64Encoding(addr); err != nil {
|
if err := validateBase64Encoding(addr); err != nil {
|
||||||
return I2PAddr(""), err
|
return I2PAddr(""), err
|
||||||
}
|
}
|
||||||
|
|
||||||
return I2PAddr(addr), nil
|
return I2PAddr(addr), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sanitizeAddress(addr string) string {
|
func sanitizeAddress(addr string) string {
|
||||||
// Remove domain suffix if present
|
// Remove domain suffix if present
|
||||||
addr = strings.TrimSuffix(addr, I2PDomainSuffix)
|
addr = strings.TrimSuffix(addr, I2PDomainSuffix)
|
||||||
return strings.Trim(addr, "\t\n\r\f ")
|
return strings.Trim(addr, "\t\n\r\f ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateAddressFormat(addr string) error {
|
func validateAddressFormat(addr string) error {
|
||||||
if len(addr) > MaxAddressLength || len(addr) < MinAddressLength {
|
if len(addr) > MaxAddressLength || len(addr) < MinAddressLength {
|
||||||
return fmt.Errorf("invalid address length: got %d, want between %d and %d",
|
return fmt.Errorf("invalid address length: got %d, want between %d and %d",
|
||||||
len(addr), MinAddressLength, MaxAddressLength)
|
len(addr), MinAddressLength, MaxAddressLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasSuffix(addr, B32DomainSuffix) {
|
if strings.HasSuffix(addr, B32DomainSuffix) {
|
||||||
return fmt.Errorf("cannot convert %s to full destination", B32DomainSuffix)
|
return fmt.Errorf("cannot convert %s to full destination", B32DomainSuffix)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateBase64Encoding(addr string) error {
|
func validateBase64Encoding(addr string) error {
|
||||||
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 {
|
||||||
return fmt.Errorf("invalid base64 encoding: %w", err)
|
return fmt.Errorf("invalid base64 encoding: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewI2PAddrFromBytes creates a new I2P address from a byte array.
|
// NewI2PAddrFromBytes creates a new I2P address from a byte array.
|
||||||
func NewI2PAddrFromBytes(addr []byte) (I2PAddr, error) {
|
func NewI2PAddrFromBytes(addr []byte) (I2PAddr, error) {
|
||||||
if len(addr) > MaxAddressLength || len(addr) < MinAddressLength {
|
if len(addr) > MaxAddressLength || len(addr) < MinAddressLength {
|
||||||
return I2PAddr(""), fmt.Errorf("invalid address length: got %d, want between %d and %d",
|
return I2PAddr(""), fmt.Errorf("invalid address length: got %d, want between %d and %d",
|
||||||
len(addr), MinAddressLength, MaxAddressLength)
|
len(addr), MinAddressLength, MaxAddressLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
encoded := make([]byte, i2pB64enc.EncodedLen(len(addr)))
|
encoded := make([]byte, i2pB64enc.EncodedLen(len(addr)))
|
||||||
i2pB64enc.Encode(encoded, addr)
|
i2pB64enc.Encode(encoded, addr)
|
||||||
return I2PAddr(encoded), nil
|
return I2PAddr(encoded), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToBytes converts the I2P address to its raw byte representation.
|
// ToBytes converts the I2P address to its raw byte representation.
|
||||||
func (addr I2PAddr) ToBytes() ([]byte, error) {
|
func (addr I2PAddr) ToBytes() ([]byte, error) {
|
||||||
decoded, err := i2pB64enc.DecodeString(string(addr))
|
decoded, err := i2pB64enc.DecodeString(string(addr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("decoding address: %w", err)
|
return nil, fmt.Errorf("decoding address: %w", err)
|
||||||
}
|
}
|
||||||
return decoded, nil
|
return decoded, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Base32 returns the *.b32.i2p representation of the address.
|
// Base32 returns the *.b32.i2p representation of the address.
|
||||||
func (addr I2PAddr) Base32() string {
|
func (addr I2PAddr) Base32() string {
|
||||||
return addr.DestHash().String()
|
return addr.DestHash().String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// DestHash computes the SHA-256 hash of the address.
|
// DestHash computes the SHA-256 hash of the address.
|
||||||
func (addr I2PAddr) DestHash() I2PDestHash {
|
func (addr I2PAddr) DestHash() I2PDestHash {
|
||||||
var hash I2PDestHash
|
var hash I2PDestHash
|
||||||
h := sha256.New()
|
h := sha256.New()
|
||||||
if bytes, err := addr.ToBytes(); err == nil {
|
if bytes, err := addr.ToBytes(); err == nil {
|
||||||
h.Write(bytes)
|
h.Write(bytes)
|
||||||
copy(hash[:], h.Sum(nil))
|
copy(hash[:], h.Sum(nil))
|
||||||
}
|
}
|
||||||
return hash
|
return hash
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ func Test_NewI2PAddrFromString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Address with .i2p suffix", func(t *testing.T) { //CHECK
|
t.Run("Address with .i2p suffix", func(t *testing.T) { // CHECK
|
||||||
addr, err := NewI2PAddrFromString(validI2PAddrB64 + ".i2p")
|
addr, err := NewI2PAddrFromString(validI2PAddrB64 + ".i2p")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewI2PAddrFromString failed for address with .i2p suffix: '%v'", err)
|
t.Fatalf("NewI2PAddrFromString failed for address with .i2p suffix: '%v'", err)
|
||||||
@ -169,15 +169,15 @@ func Test_KeyGenerationAndHandling(t *testing.T) {
|
|||||||
t.Fatalf("Failed to generate new I2P keys: %v", err)
|
t.Fatalf("Failed to generate new I2P keys: %v", err)
|
||||||
}
|
}
|
||||||
t.Run("LoadKeysIncompat", func(t *testing.T) {
|
t.Run("LoadKeysIncompat", func(t *testing.T) {
|
||||||
//extract keys
|
// extract keys
|
||||||
addr := keys.Address
|
addr := keys.Address
|
||||||
fmt.Println(addr)
|
fmt.Println(addr)
|
||||||
|
|
||||||
//both := removeNewlines(keys.Both)
|
// both := removeNewlines(keys.Both)
|
||||||
both := keys.Both
|
both := keys.Both
|
||||||
fmt.Println(both)
|
fmt.Println(both)
|
||||||
|
|
||||||
//FORMAT TO LOAD: (Address, Both)
|
// FORMAT TO LOAD: (Address, Both)
|
||||||
addrload := addr.Base64() + "\n" + both
|
addrload := addr.Base64() + "\n" + both
|
||||||
|
|
||||||
r := strings.NewReader(addrload)
|
r := strings.NewReader(addrload)
|
||||||
@ -187,9 +187,8 @@ func Test_KeyGenerationAndHandling(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if loadedKeys.Address != keys.Address {
|
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)))
|
// 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)
|
t.Errorf("LoadKeysIncompat returned incorrect address. Got '%s', want '%s'", loadedKeys.Address, keys.Address)
|
||||||
|
|
||||||
}
|
}
|
||||||
if loadedKeys.Both != keys.Both {
|
if loadedKeys.Both != keys.Both {
|
||||||
t.Errorf("LoadKeysIncompat returned incorrect pair. Got '%s'\nwant '%s'\n", loadedKeys.Both, keys.Both)
|
t.Errorf("LoadKeysIncompat returned incorrect pair. Got '%s'\nwant '%s'\n", loadedKeys.Both, keys.Both)
|
||||||
@ -199,7 +198,6 @@ func Test_KeyGenerationAndHandling(t *testing.T) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
expected := keys.Address.Base64() + "\n" + keys.Both
|
expected := keys.Address.Base64() + "\n" + keys.Both
|
||||||
|
@ -7,20 +7,20 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// HashSize is the size of an I2P destination hash in bytes
|
// HashSize is the size of an I2P destination hash in bytes
|
||||||
HashSize = 32
|
HashSize = 32
|
||||||
|
|
||||||
// B32AddressLength is the length of a base32 address without suffix
|
// B32AddressLength is the length of a base32 address without suffix
|
||||||
B32AddressLength = 52
|
B32AddressLength = 52
|
||||||
|
|
||||||
// FullB32Length is the total length of a .b32.i2p address
|
// FullB32Length is the total length of a .b32.i2p address
|
||||||
FullB32Length = 60
|
FullB32Length = 60
|
||||||
|
|
||||||
// B32Padding is the padding used for base32 encoding
|
// B32Padding is the padding used for base32 encoding
|
||||||
B32Padding = "===="
|
B32Padding = "===="
|
||||||
|
|
||||||
// B32Suffix is the standard suffix for base32 I2P addresses
|
// B32Suffix is the standard suffix for base32 I2P addresses
|
||||||
B32Suffix = ".b32.i2p"
|
B32Suffix = ".b32.i2p"
|
||||||
)
|
)
|
||||||
|
|
||||||
// I2PDestHash represents a 32-byte I2P destination hash.
|
// I2PDestHash represents a 32-byte I2P destination hash.
|
||||||
@ -30,58 +30,58 @@ type I2PDestHash [HashSize]byte
|
|||||||
// DestHashFromString creates a destination hash from a base32-encoded string.
|
// DestHashFromString creates a destination hash from a base32-encoded string.
|
||||||
// The input should be in the format "base32address.b32.i2p".
|
// The input should be in the format "base32address.b32.i2p".
|
||||||
func DestHashFromString(addr string) (I2PDestHash, error) {
|
func DestHashFromString(addr string) (I2PDestHash, error) {
|
||||||
if !isValidB32Address(addr) {
|
if !isValidB32Address(addr) {
|
||||||
return I2PDestHash{}, fmt.Errorf("invalid address format: %s", addr)
|
return I2PDestHash{}, fmt.Errorf("invalid address format: %s", addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
var hash I2PDestHash
|
var hash I2PDestHash
|
||||||
b32Input := addr[:B32AddressLength] + B32Padding
|
b32Input := addr[:B32AddressLength] + B32Padding
|
||||||
|
|
||||||
n, err := i2pB32enc.Decode(hash[:], []byte(b32Input))
|
n, err := i2pB32enc.Decode(hash[:], []byte(b32Input))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return I2PDestHash{}, fmt.Errorf("decoding base32 address: %w", err)
|
return I2PDestHash{}, fmt.Errorf("decoding base32 address: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if n != HashSize {
|
if n != HashSize {
|
||||||
return I2PDestHash{}, fmt.Errorf("decoded hash has invalid length: got %d, want %d", n, HashSize)
|
return I2PDestHash{}, fmt.Errorf("decoded hash has invalid length: got %d, want %d", n, HashSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
return hash, nil
|
return hash, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// isValidB32Address checks if the address has the correct format and length
|
// isValidB32Address checks if the address has the correct format and length
|
||||||
func isValidB32Address(addr string) bool {
|
func isValidB32Address(addr string) bool {
|
||||||
return strings.HasSuffix(addr, B32Suffix) && len(addr) == FullB32Length
|
return strings.HasSuffix(addr, B32Suffix) && len(addr) == FullB32Length
|
||||||
}
|
}
|
||||||
|
|
||||||
// DestHashFromBytes creates a destination hash from a byte slice.
|
// DestHashFromBytes creates a destination hash from a byte slice.
|
||||||
// The input must be exactly 32 bytes long.
|
// The input must be exactly 32 bytes long.
|
||||||
func DestHashFromBytes(data []byte) (I2PDestHash, error) {
|
func DestHashFromBytes(data []byte) (I2PDestHash, error) {
|
||||||
if len(data) != HashSize {
|
if len(data) != HashSize {
|
||||||
return I2PDestHash{}, fmt.Errorf("invalid hash length: got %d, want %d", len(data), HashSize)
|
return I2PDestHash{}, fmt.Errorf("invalid hash length: got %d, want %d", len(data), HashSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
var hash I2PDestHash
|
var hash I2PDestHash
|
||||||
copy(hash[:], data)
|
copy(hash[:], data)
|
||||||
return hash, nil
|
return hash, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the base32-encoded representation with the .b32.i2p suffix.
|
// String returns the base32-encoded representation with the .b32.i2p suffix.
|
||||||
func (h I2PDestHash) String() string {
|
func (h I2PDestHash) String() string {
|
||||||
encoded := make([]byte, i2pB32enc.EncodedLen(HashSize))
|
encoded := make([]byte, i2pB32enc.EncodedLen(HashSize))
|
||||||
i2pB32enc.Encode(encoded, h[:])
|
i2pB32enc.Encode(encoded, h[:])
|
||||||
return string(encoded[:B32AddressLength]) + B32Suffix
|
return string(encoded[:B32AddressLength]) + B32Suffix
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash returns the base64-encoded SHA-256 hash of the destination hash.
|
// Hash returns the base64-encoded SHA-256 hash of the destination hash.
|
||||||
func (h I2PDestHash) Hash() string {
|
func (h I2PDestHash) Hash() string {
|
||||||
digest := sha256.Sum256(h[:])
|
digest := sha256.Sum256(h[:])
|
||||||
encoded := make([]byte, i2pB64enc.EncodedLen(len(digest)))
|
encoded := make([]byte, i2pB64enc.EncodedLen(len(digest)))
|
||||||
i2pB64enc.Encode(encoded, digest[:])
|
i2pB64enc.Encode(encoded, digest[:])
|
||||||
return string(encoded[:44])
|
return string(encoded[:44])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Network returns the network type, always "I2P".
|
// Network returns the network type, always "I2P".
|
||||||
func (h I2PDestHash) Network() string {
|
func (h I2PDestHash) Network() string {
|
||||||
return "I2P"
|
return "I2P"
|
||||||
}
|
}
|
||||||
|
@ -128,6 +128,7 @@ func StoreKeysIncompat(k I2PKeys, w io.Writer) error {
|
|||||||
log.WithField("keys", k).Debug("Keys stored successfully")
|
log.WithField("keys", k).Debug("Keys stored successfully")
|
||||||
return nil
|
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")
|
log.WithField("filename", r).Debug("Storing keys to file")
|
||||||
if _, err := os.Stat(r); err != nil {
|
if _, err := os.Stat(r); err != nil {
|
||||||
@ -190,7 +191,7 @@ func (k I2PKeys) PrivateKey() crypto.PrivateKey {
|
|||||||
_, 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")
|
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
|
||||||
}
|
}
|
||||||
@ -225,4 +226,3 @@ func (k I2PKeys) HostnameEntry(hostname string, opts crypto.SignerOpts) (string,
|
|||||||
}
|
}
|
||||||
return string(sig), nil
|
return string(sig), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
4
log.go
4
log.go
@ -4,9 +4,7 @@ import (
|
|||||||
"github.com/go-i2p/logger"
|
"github.com/go-i2p/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var log *logger.Logger
|
||||||
log *logger.Logger
|
|
||||||
)
|
|
||||||
|
|
||||||
func InitializeI2PKeysLogger() {
|
func InitializeI2PKeysLogger() {
|
||||||
logger.InitializeGoI2PLogger()
|
logger.InitializeGoI2PLogger()
|
||||||
|
187
new.go
187
new.go
@ -9,145 +9,146 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var DefaultSAMAddress = "127.0.0.1:7656"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultSAMAddress = "127.0.0.1:7656"
|
defaultTimeout = 30 * time.Second
|
||||||
defaultTimeout = 30 * time.Second
|
maxResponseSize = 4096
|
||||||
maxResponseSize = 4096
|
|
||||||
|
cmdHello = "HELLO VERSION MIN=3.1 MAX=3.1\n"
|
||||||
cmdHello = "HELLO VERSION MIN=3.1 MAX=3.1\n"
|
cmdGenerate = "DEST GENERATE SIGNATURE_TYPE=7\n"
|
||||||
cmdGenerate = "DEST GENERATE SIGNATURE_TYPE=7\n"
|
responseOK = "RESULT=OK"
|
||||||
responseOK = "RESULT=OK"
|
pubKeyPrefix = "PUB="
|
||||||
pubKeyPrefix = "PUB="
|
privKeyPrefix = "PRIV="
|
||||||
privKeyPrefix = "PRIV="
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// samClient handles communication with the SAM bridge
|
// samClient handles communication with the SAM bridge
|
||||||
type samClient struct {
|
type samClient struct {
|
||||||
addr string
|
addr string
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// newSAMClient creates a new SAM client with optional configuration
|
// newSAMClient creates a new SAM client with optional configuration
|
||||||
func newSAMClient(options ...func(*samClient)) *samClient {
|
func newSAMClient(options ...func(*samClient)) *samClient {
|
||||||
client := &samClient{
|
client := &samClient{
|
||||||
addr: defaultSAMAddress,
|
addr: DefaultSAMAddress,
|
||||||
timeout: defaultTimeout,
|
timeout: defaultTimeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, opt := range options {
|
for _, opt := range options {
|
||||||
opt(client)
|
opt(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDestination generates a new I2P destination using the SAM bridge.
|
// NewDestination generates a new I2P destination using the SAM bridge.
|
||||||
// This is the only public function that external code should use.
|
// This is the only public function that external code should use.
|
||||||
func NewDestination() (*I2PKeys, error) {
|
func NewDestination() (*I2PKeys, error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
client := newSAMClient()
|
client := newSAMClient()
|
||||||
return client.generateDestination(ctx)
|
return client.generateDestination(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateDestination handles the key generation process
|
// generateDestination handles the key generation process
|
||||||
func (c *samClient) generateDestination(ctx context.Context) (*I2PKeys, error) {
|
func (c *samClient) generateDestination(ctx context.Context) (*I2PKeys, error) {
|
||||||
conn, err := c.dial(ctx)
|
conn, err := c.dial(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("connecting to SAM bridge: %w", err)
|
return nil, fmt.Errorf("connecting to SAM bridge: %w", err)
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
if err := c.handshake(ctx, conn); err != nil {
|
if err := c.handshake(ctx, conn); err != nil {
|
||||||
return nil, fmt.Errorf("SAM handshake failed: %w", err)
|
return nil, fmt.Errorf("SAM handshake failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
keys, err := c.generateKeys(ctx, conn)
|
keys, err := c.generateKeys(ctx, conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("generating keys: %w", err)
|
return nil, fmt.Errorf("generating keys: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return keys, nil
|
return keys, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *samClient) dial(ctx context.Context) (net.Conn, error) {
|
func (c *samClient) dial(ctx context.Context) (net.Conn, error) {
|
||||||
dialer := &net.Dialer{Timeout: c.timeout}
|
dialer := &net.Dialer{Timeout: c.timeout}
|
||||||
conn, err := dialer.DialContext(ctx, "tcp", c.addr)
|
conn, err := dialer.DialContext(ctx, "tcp", c.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("dialing SAM bridge: %w", err)
|
return nil, fmt.Errorf("dialing SAM bridge: %w", err)
|
||||||
}
|
}
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *samClient) handshake(ctx context.Context, conn net.Conn) error {
|
func (c *samClient) handshake(ctx context.Context, conn net.Conn) error {
|
||||||
if err := c.writeCommand(conn, cmdHello); err != nil {
|
if err := c.writeCommand(conn, cmdHello); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := c.readResponse(conn)
|
response, err := c.readResponse(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(response, responseOK) {
|
if !strings.Contains(response, responseOK) {
|
||||||
return fmt.Errorf("unexpected SAM response: %s", response)
|
return fmt.Errorf("unexpected SAM response: %s", response)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *samClient) generateKeys(ctx context.Context, conn net.Conn) (*I2PKeys, error) {
|
func (c *samClient) generateKeys(ctx context.Context, conn net.Conn) (*I2PKeys, error) {
|
||||||
if err := c.writeCommand(conn, cmdGenerate); err != nil {
|
if err := c.writeCommand(conn, cmdGenerate); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := c.readResponse(conn)
|
response, err := c.readResponse(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pub, priv, err := parseKeyResponse(response)
|
pub, priv, err := parseKeyResponse(response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &I2PKeys{
|
return &I2PKeys{
|
||||||
Address: I2PAddr(pub),
|
Address: I2PAddr(pub),
|
||||||
Both: pub + priv,
|
Both: pub + priv,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *samClient) writeCommand(conn net.Conn, cmd string) error {
|
func (c *samClient) writeCommand(conn net.Conn, cmd string) error {
|
||||||
_, err := conn.Write([]byte(cmd))
|
_, err := conn.Write([]byte(cmd))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("writing command: %w", err)
|
return fmt.Errorf("writing command: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *samClient) readResponse(conn net.Conn) (string, error) {
|
func (c *samClient) readResponse(conn net.Conn) (string, error) {
|
||||||
reader := bufio.NewReader(conn)
|
reader := bufio.NewReader(conn)
|
||||||
response, err := reader.ReadString('\n')
|
response, err := reader.ReadString('\n')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("reading response: %w", err)
|
return "", fmt.Errorf("reading response: %w", err)
|
||||||
}
|
}
|
||||||
return strings.TrimSpace(response), nil
|
return strings.TrimSpace(response), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseKeyResponse(response string) (pub, priv string, err error) {
|
func parseKeyResponse(response string) (pub, priv string, err error) {
|
||||||
parts := strings.Split(response, privKeyPrefix)
|
parts := strings.Split(response, privKeyPrefix)
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
return "", "", fmt.Errorf("invalid key response format")
|
return "", "", fmt.Errorf("invalid key response format")
|
||||||
}
|
}
|
||||||
|
|
||||||
pubParts := strings.Split(parts[0], pubKeyPrefix)
|
pubParts := strings.Split(parts[0], pubKeyPrefix)
|
||||||
if len(pubParts) != 2 {
|
if len(pubParts) != 2 {
|
||||||
return "", "", fmt.Errorf("invalid public key format")
|
return "", "", fmt.Errorf("invalid public key format")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub = strings.TrimSpace(pubParts[1])
|
pub = strings.TrimSpace(pubParts[1])
|
||||||
priv = strings.TrimSpace(parts[1])
|
priv = strings.TrimSpace(parts[1])
|
||||||
|
|
||||||
return pub, priv, nil
|
return pub, priv, nil
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user