mirror of
https://github.com/go-i2p/i2pkeys.git
synced 2025-06-08 01:09:12 -04:00
135 lines
3.6 KiB
Go
135 lines
3.6 KiB
Go
package i2pkeys
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
// Address length constraints
|
|
MinAddressLength = 516
|
|
MaxAddressLength = 4096
|
|
|
|
// Domain suffixes
|
|
I2PDomainSuffix = ".i2p"
|
|
)
|
|
|
|
// I2PAddr represents an I2P destination, equivalent to an IP address.
|
|
// It contains a base64-encoded representation of public keys and optional certificates.
|
|
type I2PAddr string
|
|
|
|
// Base64 returns the raw base64 representation of the I2P address.
|
|
func (a I2PAddr) Base64() string {
|
|
return string(a)
|
|
}
|
|
|
|
// String returns either the base64 or base32 representation based on configuration.
|
|
func (a I2PAddr) String() string {
|
|
if StringIsBase64 {
|
|
return a.Base64()
|
|
}
|
|
return a.Base32()
|
|
}
|
|
|
|
// Network returns the network type, always "I2P".
|
|
func (a I2PAddr) Network() string {
|
|
return "I2P"
|
|
}
|
|
|
|
// NewI2PAddrFromString creates a new I2P address from a base64-encoded string.
|
|
// It validates the format and returns an error if the address is invalid.
|
|
func NewI2PAddrFromString(addr string) (I2PAddr, error) {
|
|
addr = sanitizeAddress(addr)
|
|
|
|
if err := validateAddressFormat(addr); err != nil {
|
|
return I2PAddr(""), err
|
|
}
|
|
|
|
if err := validateBase64Encoding(addr); err != nil {
|
|
return I2PAddr(""), err
|
|
}
|
|
|
|
return I2PAddr(addr), nil
|
|
}
|
|
|
|
func sanitizeAddress(addr string) string {
|
|
// Remove domain suffix if present
|
|
addr = strings.TrimSuffix(addr, I2PDomainSuffix)
|
|
return strings.Trim(addr, "\t\n\r\f ")
|
|
}
|
|
|
|
func validateAddressFormat(addr string) error {
|
|
host, _, err := net.SplitHostPort(addr)
|
|
if err == nil {
|
|
// Successfully split host:port, use just the host part
|
|
addr = host
|
|
}
|
|
|
|
if len(addr) > MaxAddressLength || len(addr) < MinAddressLength {
|
|
return fmt.Errorf("invalid address length: got %d, want between %d and %d",
|
|
len(addr), MinAddressLength, MaxAddressLength)
|
|
}
|
|
if strings.HasSuffix(addr, B32Suffix) {
|
|
return fmt.Errorf("cannot convert %s to full destination", B32Suffix)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateBase64Encoding(addr string) error {
|
|
// Use DecodeString which handles buffer allocation internally
|
|
// and returns the actual decoded bytes, providing better validation
|
|
decoded, err := i2pB64enc.DecodeString(addr)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid base64 encoding: %w", err)
|
|
}
|
|
|
|
// Validate that we got a reasonable amount of decoded data
|
|
// This prevents edge cases where decoding succeeds but produces empty/minimal output
|
|
if len(decoded) == 0 {
|
|
return fmt.Errorf("base64 decoding produced empty result")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// NewI2PAddrFromBytes creates a new I2P address from a byte array.
|
|
func NewI2PAddrFromBytes(addr []byte) (I2PAddr, error) {
|
|
// Calculate the expected encoded length to validate against string constraints
|
|
encodedLen := i2pB64enc.EncodedLen(len(addr))
|
|
if encodedLen > MaxAddressLength || encodedLen < MinAddressLength {
|
|
return I2PAddr(""), fmt.Errorf("invalid address length: encoded length %d, want between %d and %d",
|
|
encodedLen, MinAddressLength, MaxAddressLength)
|
|
}
|
|
|
|
encoded := make([]byte, encodedLen)
|
|
i2pB64enc.Encode(encoded, addr)
|
|
return I2PAddr(encoded), nil
|
|
}
|
|
|
|
// ToBytes converts the I2P address to its raw byte representation.
|
|
func (addr I2PAddr) ToBytes() ([]byte, error) {
|
|
decoded, err := i2pB64enc.DecodeString(string(addr))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("decoding address: %w", err)
|
|
}
|
|
return decoded, nil
|
|
}
|
|
|
|
// Base32 returns the *.b32.i2p representation of the address.
|
|
func (addr I2PAddr) Base32() string {
|
|
return addr.DestHash().String()
|
|
}
|
|
|
|
// DestHash computes the SHA-256 hash of the address.
|
|
func (addr I2PAddr) DestHash() I2PDestHash {
|
|
var hash I2PDestHash
|
|
h := sha256.New()
|
|
if bytes, err := addr.ToBytes(); err == nil {
|
|
h.Write(bytes)
|
|
copy(hash[:], h.Sum(nil))
|
|
}
|
|
return hash
|
|
}
|