Split I2PAddr and I2PDestHash into their own files
This commit is contained in:
352
I2PAddr.go
352
I2PAddr.go
@ -1,298 +1,17 @@
|
|||||||
package i2pkeys
|
package i2pkeys
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto"
|
|
||||||
"crypto/ed25519"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base32"
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"strings"
|
"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.
|
|
||||||
func (k I2PKeys) Addr() I2PAddr {
|
|
||||||
return k.Address
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
type SecretKey interface {
|
|
||||||
Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k I2PKeys) SecretKey() SecretKey {
|
|
||||||
var pk ed25519.PrivateKey = k.Private()
|
|
||||||
return pk
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k I2PKeys) PrivateKey() crypto.PrivateKey {
|
|
||||||
var pk ed25519.PrivateKey = k.Private()
|
|
||||||
_, err := pk.Sign(rand.Reader, []byte("nonsense"), crypto.Hash(0))
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Warn("Error in private key signature")
|
|
||||||
//TODO: Elgamal, P256, P384, P512, GOST? keys?
|
|
||||||
}
|
|
||||||
return pk
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k I2PKeys) Ed25519PrivateKey() *ed25519.PrivateKey {
|
|
||||||
return k.SecretKey().(*ed25519.PrivateKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*func (k I2PKeys) ElgamalPrivateKey() *ed25519.PrivateKey {
|
|
||||||
return k.SecretKey().(*ed25519.PrivateKey)
|
|
||||||
}*/
|
|
||||||
|
|
||||||
//func (k I2PKeys) Decrypt(rand io.Reader, msg []byte, opts crypto.DecrypterOpts) (plaintext []byte, err error) {
|
|
||||||
//return k.SecretKey().(*ed25519.PrivateKey).Decrypt(rand, msg, opts)
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (k I2PKeys) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
|
||||||
return k.SecretKey().(*ed25519.PrivateKey).Sign(rand, digest, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k I2PKeys) HostnameEntry(hostname string, opts crypto.SignerOpts) (string, error) {
|
|
||||||
sig, err := k.Sign(rand.Reader, []byte(hostname), opts)
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Error("Error signing hostname")
|
|
||||||
return "", fmt.Errorf("error signing hostname: %w", err)
|
|
||||||
}
|
|
||||||
return string(sig), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// I2PAddr represents an I2P destination, almost equivalent to an IP address.
|
// I2PAddr represents an I2P destination, almost equivalent to an IP address.
|
||||||
// This is the humongously huge base64 representation of such an address, which
|
// This is the humongously huge base64 representation of such an address, which
|
||||||
// really is just a pair of public keys and also maybe a certificate. (I2P hides
|
// really is just a pair of public keys and also maybe a certificate. (I2P hides
|
||||||
// the details of exactly what it is. Read the I2P specifications for more info.)
|
// the details of exactly what it is. Read the I2P specifications for more info.)
|
||||||
type I2PAddr string
|
type I2PAddr string
|
||||||
|
|
||||||
// an i2p destination hash, the .b32.i2p address if you will
|
|
||||||
type I2PDestHash [32]byte
|
|
||||||
|
|
||||||
// create a desthash from a string b32.i2p address
|
|
||||||
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 {
|
|
||||||
// valid
|
|
||||||
_, err = i2pB32enc.Decode(dhash[:], []byte(str[:52]+"===="))
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Error("Error decoding base32 address")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// invalid
|
|
||||||
err = errors.New("invalid desthash format")
|
|
||||||
log.WithError(err).Error("Invalid desthash format")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a desthash from a []byte array
|
|
||||||
func DestHashFromBytes(str []byte) (dhash I2PDestHash, err error) {
|
|
||||||
log.Debug("Creating DestHash from bytes")
|
|
||||||
if len(str) == 32 {
|
|
||||||
// valid
|
|
||||||
//_, err = i2pB32enc.Decode(dhash[:], []byte(str[:52]+"===="))
|
|
||||||
log.WithField("str", str).Debug("Copying str to desthash")
|
|
||||||
copy(dhash[:], str)
|
|
||||||
} else {
|
|
||||||
// invalid
|
|
||||||
err = errors.New("invalid desthash format")
|
|
||||||
log.WithField("str", str).Error("Invalid desthash format")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// get string representation of i2p dest hash(base32 version)
|
|
||||||
func (h I2PDestHash) String() string {
|
|
||||||
b32addr := make([]byte, 56)
|
|
||||||
i2pB32enc.Encode(b32addr, h[:])
|
|
||||||
return string(b32addr[:52]) + ".b32.i2p"
|
|
||||||
}
|
|
||||||
|
|
||||||
// get base64 representation of i2p dest sha256 hash(the 44-character one)
|
|
||||||
func (h I2PDestHash) Hash() string {
|
|
||||||
hash := sha256.New()
|
|
||||||
hash.Write(h[:])
|
|
||||||
digest := hash.Sum(nil)
|
|
||||||
buf := make([]byte, 44)
|
|
||||||
i2pB64enc.Encode(buf, digest)
|
|
||||||
return string(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns "I2P"
|
|
||||||
func (h I2PDestHash) Network() string {
|
|
||||||
return "I2P"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the base64 representation of the I2PAddr
|
// Returns the base64 representation of the I2PAddr
|
||||||
func (a I2PAddr) Base64() string {
|
func (a I2PAddr) Base64() string {
|
||||||
return string(a)
|
return string(a)
|
||||||
@ -395,74 +114,3 @@ func (addr I2PAddr) DestHash() (h I2PDestHash) {
|
|||||||
func Base32(anything string) string {
|
func Base32(anything string) string {
|
||||||
return I2PAddr(anything).Base32()
|
return I2PAddr(anything).Base32()
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
HELLO VERSION MIN=3.1 MAX=3.1
|
|
||||||
DEST GENERATE SIGNATURE_TYPE=7
|
|
||||||
*/
|
|
||||||
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")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
_, err = conn.Write([]byte("HELLO VERSION MIN=3.1 MAX=3.1\n"))
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Error("Error writing to SAM bridge")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
buf := make([]byte, 4096)
|
|
||||||
n, err := conn.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Error("Error reading from SAM bridge")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if n < 1 {
|
|
||||||
log.Error("No data received from SAM bridge")
|
|
||||||
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") {
|
|
||||||
_, err = conn.Write([]byte("DEST GENERATE SIGNATURE_TYPE=7\n"))
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Error("Error writing DEST GENERATE to SAM bridge")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
n, err = conn.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Error("Error reading destination from SAM bridge")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if n < 1 {
|
|
||||||
log.Error("No destination data received from SAM bridge")
|
|
||||||
return nil, fmt.Errorf("no destination data received")
|
|
||||||
}
|
|
||||||
pub := strings.Split(strings.Split(string(buf[:n]), "PRIV=")[0], "PUB=")[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{
|
|
||||||
Address: I2PAddr(pub),
|
|
||||||
Both: pub + priv,
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
log.Error("No RESULT=OK received from SAM bridge")
|
|
||||||
return nil, fmt.Errorf("no result received")
|
|
||||||
}
|
|
||||||
|
65
I2PDestHash.go
Normal file
65
I2PDestHash.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package i2pkeys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// an i2p destination hash, the .b32.i2p address if you will
|
||||||
|
type I2PDestHash [32]byte
|
||||||
|
|
||||||
|
// create a desthash from a string b32.i2p address
|
||||||
|
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 {
|
||||||
|
// valid
|
||||||
|
_, err = i2pB32enc.Decode(dhash[:], []byte(str[:52]+"===="))
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Error("Error decoding base32 address")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// invalid
|
||||||
|
err = errors.New("invalid desthash format")
|
||||||
|
log.WithError(err).Error("Invalid desthash format")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a desthash from a []byte array
|
||||||
|
func DestHashFromBytes(str []byte) (dhash I2PDestHash, err error) {
|
||||||
|
log.Debug("Creating DestHash from bytes")
|
||||||
|
if len(str) == 32 {
|
||||||
|
// valid
|
||||||
|
//_, err = i2pB32enc.Decode(dhash[:], []byte(str[:52]+"===="))
|
||||||
|
log.WithField("str", str).Debug("Copying str to desthash")
|
||||||
|
copy(dhash[:], str)
|
||||||
|
} else {
|
||||||
|
// invalid
|
||||||
|
err = errors.New("invalid desthash format")
|
||||||
|
log.WithField("str", str).Error("Invalid desthash format")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get string representation of i2p dest hash(base32 version)
|
||||||
|
func (h I2PDestHash) String() string {
|
||||||
|
b32addr := make([]byte, 56)
|
||||||
|
i2pB32enc.Encode(b32addr, h[:])
|
||||||
|
return string(b32addr[:52]) + ".b32.i2p"
|
||||||
|
}
|
||||||
|
|
||||||
|
// get base64 representation of i2p dest sha256 hash(the 44-character one)
|
||||||
|
func (h I2PDestHash) Hash() string {
|
||||||
|
hash := sha256.New()
|
||||||
|
hash.Write(h[:])
|
||||||
|
digest := hash.Sum(nil)
|
||||||
|
buf := make([]byte, 44)
|
||||||
|
i2pB64enc.Encode(buf, digest)
|
||||||
|
return string(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns "I2P"
|
||||||
|
func (h I2PDestHash) Network() string {
|
||||||
|
return "I2P"
|
||||||
|
}
|
301
I2PKeys.go
Normal file
301
I2PKeys.go
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
package i2pkeys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base32"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
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.
|
||||||
|
func (k I2PKeys) Addr() I2PAddr {
|
||||||
|
return k.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
type SecretKey interface {
|
||||||
|
Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k I2PKeys) SecretKey() SecretKey {
|
||||||
|
var pk ed25519.PrivateKey = k.Private()
|
||||||
|
return pk
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k I2PKeys) PrivateKey() crypto.PrivateKey {
|
||||||
|
var pk ed25519.PrivateKey = k.Private()
|
||||||
|
_, err := pk.Sign(rand.Reader, []byte("nonsense"), crypto.Hash(0))
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Warn("Error in private key signature")
|
||||||
|
//TODO: Elgamal, P256, P384, P512, GOST? keys?
|
||||||
|
}
|
||||||
|
return pk
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k I2PKeys) Ed25519PrivateKey() *ed25519.PrivateKey {
|
||||||
|
return k.SecretKey().(*ed25519.PrivateKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*func (k I2PKeys) ElgamalPrivateKey() *ed25519.PrivateKey {
|
||||||
|
return k.SecretKey().(*ed25519.PrivateKey)
|
||||||
|
}*/
|
||||||
|
|
||||||
|
//func (k I2PKeys) Decrypt(rand io.Reader, msg []byte, opts crypto.DecrypterOpts) (plaintext []byte, err error) {
|
||||||
|
//return k.SecretKey().(*ed25519.PrivateKey).Decrypt(rand, msg, opts)
|
||||||
|
//}
|
||||||
|
|
||||||
|
func (k I2PKeys) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||||
|
return k.SecretKey().(*ed25519.PrivateKey).Sign(rand, digest, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k I2PKeys) HostnameEntry(hostname string, opts crypto.SignerOpts) (string, error) {
|
||||||
|
sig, err := k.Sign(rand.Reader, []byte(hostname), opts)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Error("Error signing hostname")
|
||||||
|
return "", fmt.Errorf("error signing hostname: %w", err)
|
||||||
|
}
|
||||||
|
return string(sig), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
HELLO VERSION MIN=3.1 MAX=3.1
|
||||||
|
DEST GENERATE SIGNATURE_TYPE=7
|
||||||
|
*/
|
||||||
|
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")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
_, err = conn.Write([]byte("HELLO VERSION MIN=3.1 MAX=3.1\n"))
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Error("Error writing to SAM bridge")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buf := make([]byte, 4096)
|
||||||
|
n, err := conn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Error("Error reading from SAM bridge")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if n < 1 {
|
||||||
|
log.Error("No data received from SAM bridge")
|
||||||
|
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") {
|
||||||
|
_, err = conn.Write([]byte("DEST GENERATE SIGNATURE_TYPE=7\n"))
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Error("Error writing DEST GENERATE to SAM bridge")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
n, err = conn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Error("Error reading destination from SAM bridge")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if n < 1 {
|
||||||
|
log.Error("No destination data received from SAM bridge")
|
||||||
|
return nil, fmt.Errorf("no destination data received")
|
||||||
|
}
|
||||||
|
pub := strings.Split(strings.Split(string(buf[:n]), "PRIV=")[0], "PUB=")[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{
|
||||||
|
Address: I2PAddr(pub),
|
||||||
|
Both: pub + priv,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
log.Error("No RESULT=OK received from SAM bridge")
|
||||||
|
return nil, fmt.Errorf("no result received")
|
||||||
|
}
|
Reference in New Issue
Block a user