package i2pkeys import ( "bytes" "crypto" "encoding/base32" "encoding/base64" "errors" "fmt" "io" "os" "strings" ) var ( i2pB64enc *base64.Encoding = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~") 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 // 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 // a certificate. String() returns you the full content of I2PKeys and Addr() // returns the public keys. type I2PKeys struct { Address I2PAddr // only the public key Both string // both public and private keys } // Creates I2PKeys from an I2PAddr and a public/private keypair string (as // generated by String().) func NewKeys(addr I2PAddr, both string) I2PKeys { log.WithField("addr", addr).Debug("Creating new I2PKeys") return I2PKeys{addr, both} } // fileExists checks if a file exists and is not a directory before we // try using it to prevent further errors. func fileExists(filename string) (bool, error) { info, err := os.Stat(filename) if os.IsNotExist(err) { log.WithField("filename", filename).Debug("File does not exist") return false, nil } else if err != nil { 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 } // LoadKeysIncompat loads keys from a non-standard format func LoadKeysIncompat(r io.Reader) (I2PKeys, error) { log.Debug("Loading keys from reader") var buff bytes.Buffer _, err := io.Copy(&buff, r) if err != nil { log.WithError(err).Error("Error copying from reader, did not load keys") return I2PKeys{}, fmt.Errorf("error copying from reader: %w", err) } 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. // If the file does not exist, generate keys, otherwise, fail // closed. func LoadKeys(r string) (I2PKeys, error) { log.WithField("filename", r).Debug("Loading keys from file") exists, err := fileExists(r) if err != nil { log.WithError(err).Error("Error checking if file exists") return I2PKeys{}, err } 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) 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 func StoreKeysIncompat(k I2PKeys, w io.Writer) error { log.Debug("Storing keys") _, 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 { log.WithField("filename", r).Debug("Storing keys to file") if _, err := os.Stat(r); err != nil { if os.IsNotExist(err) { log.WithField("filename", r).Debug("File does not exist, creating new file") fi, err := os.Create(r) if err != nil { log.WithError(err).Error("Error creating file") return err } defer fi.Close() return StoreKeysIncompat(k, fi) } } fi, err := os.Open(r) if err != nil { log.WithError(err).Error("Error opening file") return err } defer fi.Close() return StoreKeysIncompat(k, fi) } func (k I2PKeys) Network() string { return k.Address.Network() } // Returns the public keys of the I2PKeys in Addr form func (k I2PKeys) Addr() I2PAddr { return k.Address } // Returns the public keys of the I2PKeys. func (k I2PKeys) Public() crypto.PublicKey { return k.Address } func (k I2PKeys) Private() []byte { log.Debug("Extracting private key") src := strings.Split(k.String(), k.Addr().String())[0] var dest []byte _, err := i2pB64enc.Decode(dest, []byte(src)) if err != nil { log.WithError(err).Error("Error decoding private key") panic(err) } return dest } // Returns the keys (both public and private), in I2Ps base64 format. Use this // when you create sessions. func (k I2PKeys) String() string { return k.Both }