add appliation

This commit is contained in:
eyedeekay
2025-04-21 17:37:08 -04:00
parent 1b14c80256
commit 747e6d04f8
3 changed files with 345 additions and 0 deletions

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module github.com/go-i2p/i2pkeys-converter
go 1.24.2

222
i2pkeys/extractor.go Normal file
View File

@ -0,0 +1,222 @@
// Package i2pkeys provides utilities for working with I2P key formats
package i2pkeys
import (
"encoding/base64"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
)
// I2P uses a custom Base64 encoding with '-' and '~' instead of '+' and '/'
var i2pB64Encoding = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~")
// KeyPair represents an I2P key pair with both public and private components
type KeyPair struct {
PublicKey []byte // The destination (public key)
PrivateKey []byte // The private key
FullData []byte // The complete key data
}
// ConvertKeyFile converts an I2P binary key file to the two-line format required by Go I2P
func ConvertKeyFile(inputPath, outputPath string) error {
// Read the key file as binary data
data, err := os.ReadFile(inputPath)
if err != nil {
return fmt.Errorf("failed to read key file: %w", err)
}
// Check if input is already in the expected format
if IsCorrectFormat(string(data)) {
// Create output directory if needed
outputDir := filepath.Dir(outputPath)
if err := os.MkdirAll(outputDir, 0755); err != nil {
return fmt.Errorf("failed to create output directory: %w", err)
}
// Just copy the file as is
if err := os.WriteFile(outputPath, data, 0600); err != nil {
return fmt.Errorf("failed to write output file: %w", err)
}
return nil
}
// Try to extract public key information if it's in I2P Base64 format
keyData := string(data)
var formattedOutput string
// If data is in I2P Base64 format, try to extract the public key portion
if isI2PBase64Format(keyData) {
// Split by newlines in case there are multiple keys
lines := strings.Split(keyData, "\n")
completeKey := lines[0]
// For I2P tunnel keys, the public key is the first 516 characters
// This is a heuristic based on the standard format of I2P keys
if len(completeKey) >= 516 {
publicPart := completeKey[:516]
formattedOutput = publicPart + "\n" + completeKey
} else {
// If we can't extract, convert the entire binary file
completeKey = toI2PBase64(data)
// Public key is typically the first 516 characters
if len(completeKey) >= 516 {
publicPart := completeKey[:516]
formattedOutput = publicPart + "\n" + completeKey
} else {
return errors.New("key data too short to extract public key portion")
}
}
} else {
// Not in Base64 format, treat as binary and convert
completeKey := toI2PBase64(data)
// Public key is typically the first 516 characters
if len(completeKey) >= 516 {
publicPart := completeKey[:516]
formattedOutput = publicPart + "\n" + completeKey
} else {
return errors.New("key data too short to extract public key portion")
}
}
// Create output directory if needed
outputDir := filepath.Dir(outputPath)
if err := os.MkdirAll(outputDir, 0755); err != nil {
return fmt.Errorf("failed to create output directory: %w", err)
}
// Write formatted output to file
if err := os.WriteFile(outputPath, []byte(formattedOutput), 0600); err != nil {
return fmt.Errorf("failed to write output file: %w", err)
}
return nil
}
// IsCorrectFormat checks if the data is already in the correct two-line format
func IsCorrectFormat(data string) bool {
lines := strings.Split(strings.TrimSpace(data), "\n")
if len(lines) != 2 {
return false
}
// Check if both lines appear to be valid I2P Base64
return isI2PBase64Format(lines[0]) && isI2PBase64Format(lines[1])
}
// isI2PBase64Format checks if a string appears to be in I2P Base64 format
func isI2PBase64Format(data string) bool {
// Remove whitespace
data = strings.TrimSpace(data)
if data == "" {
return false
}
// Check for I2P Base64 character set
for _, r := range data {
if !((r >= 'A' && r <= 'Z') ||
(r >= 'a' && r <= 'z') ||
(r >= '0' && r <= '9') ||
r == '-' || r == '~' || r == '=') {
return false
}
}
// Try to decode
_, err := fromI2PBase64(data)
return err == nil
}
// toI2PBase64 converts binary data to I2P's Base64 variant
func toI2PBase64(data []byte) string {
return i2pB64Encoding.EncodeToString(data)
}
// fromI2PBase64 converts I2P Base64 format back to binary
func fromI2PBase64(i2pBase64 string) ([]byte, error) {
return i2pB64Encoding.DecodeString(i2pBase64)
}
// FormatKeysFile formats an existing I2P Base64 key into the proper two-line format
func FormatKeysFile(inputPath, outputPath string) error {
// Read the key file
data, err := os.ReadFile(inputPath)
if err != nil {
return fmt.Errorf("failed to read key file: %w", err)
}
// Check if it's already in the correct format
if IsCorrectFormat(string(data)) {
// Already in the correct format, just copy
if inputPath != outputPath {
if err := os.WriteFile(outputPath, data, 0600); err != nil {
return fmt.Errorf("failed to write output file: %w", err)
}
}
return nil
}
// Clean the input
cleanedInput := cleanI2PBase64(string(data))
// Split by lines (there might be multiple keys)
lines := strings.Split(cleanedInput, "\n")
// Process the first non-empty line
var completeKey string
for _, line := range lines {
if strings.TrimSpace(line) != "" {
completeKey = line
break
}
}
// Ensure we have enough data
if len(completeKey) < 516 {
return errors.New("key data too short to format correctly")
}
// Extract public key (first 516 characters)
publicPart := completeKey[:516]
// Create the proper two-line format
formattedOutput := publicPart + "\n" + completeKey
// Create output directory if needed
outputDir := filepath.Dir(outputPath)
if err := os.MkdirAll(outputDir, 0755); err != nil {
return fmt.Errorf("failed to create output directory: %w", err)
}
// Write to output file
if err := os.WriteFile(outputPath, []byte(formattedOutput), 0600); err != nil {
return fmt.Errorf("failed to write output file: %w", err)
}
return nil
}
// cleanI2PBase64 cleans a string to ensure it only contains valid I2P Base64 characters
func cleanI2PBase64(data string) string {
// Remove whitespace
data = strings.TrimSpace(data)
// Clean the line of any invalid characters
var cleaned strings.Builder
for _, r := range data {
if (r >= 'A' && r <= 'Z') ||
(r >= 'a' && r <= 'z') ||
(r >= '0' && r <= '9') ||
r == '-' || r == '~' || r == '=' ||
r == '\n' {
cleaned.WriteRune(r)
}
}
return cleaned.String()
}

120
main.go Normal file
View File

@ -0,0 +1,120 @@
package main
import (
"flag"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/go-i2p/i2pkeys-converter/i2pkeys"
)
func main() {
// Command line arguments
inputFile := flag.String("in", "", "Path to the I2P key file (required)")
outputFile := flag.String("out", "", "Path to save the formatted key (optional)")
verbose := flag.Bool("v", false, "Verbose output with key details")
checkFormat := flag.Bool("check", false, "Check if a file is already in the correct format")
// Custom usage message
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "I2P Keys Converter - Format I2P keys for Go I2P libraries\n\n")
fmt.Fprintf(os.Stderr, "Usage: %s -in keyfile [-out outputfile] [-v] [-check]\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, "Options:\n")
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, "\nExamples:\n")
fmt.Fprintf(os.Stderr, " Convert binary key file: %s -in keys.dat -out keys.dat.formatted\n", os.Args[0])
fmt.Fprintf(os.Stderr, " Check key file format: %s -in keys.dat -check\n", os.Args[0])
fmt.Fprintf(os.Stderr, " Format with verbose info: %s -in keys.dat -v\n", os.Args[0])
}
flag.Parse()
// Validate input file parameter
if *inputFile == "" {
fmt.Println("Error: Input file (-in) is required")
flag.Usage()
os.Exit(1)
}
// Check if input file exists
if _, err := os.Stat(*inputFile); os.IsNotExist(err) {
fmt.Printf("Error: Input file '%s' does not exist\n", *inputFile)
os.Exit(1)
}
// If check mode is enabled, just check the format
if *checkFormat {
data, err := os.ReadFile(*inputFile)
if err != nil {
fmt.Printf("Error reading file: %s\n", err)
os.Exit(1)
}
if i2pkeys.IsCorrectFormat(string(data)) {
fmt.Println("File IS in the correct two-line format")
os.Exit(0)
} else {
fmt.Println("File is NOT in the correct two-line format")
os.Exit(1)
}
}
// Set default output file if not specified
if *outputFile == "" {
baseName := filepath.Base(*inputFile)
dir := filepath.Dir(*inputFile)
*outputFile = filepath.Join(dir, baseName+".formatted")
}
// Print operation info
fmt.Printf("Formatting I2P key file: %s\n", *inputFile)
fmt.Printf("Output file: %s\n", *outputFile)
// Convert the key file
err := i2pkeys.ConvertKeyFile(*inputFile, *outputFile)
if err != nil {
fmt.Printf("Error: %s\n", err)
os.Exit(1)
}
// Verify the result
resultData, err := os.ReadFile(*outputFile)
if err != nil {
fmt.Printf("Error reading result file: %s\n", err)
os.Exit(1)
}
if i2pkeys.IsCorrectFormat(string(resultData)) {
fmt.Println("Conversion successful - key is now in the correct format")
// Display additional information if verbose mode is enabled
if *verbose {
lines := strings.Split(string(resultData), "\n")
if len(lines) >= 2 {
publicKeyPreview := truncateString(lines[0], 40)
fullKeyPreview := truncateString(lines[1], 40)
fmt.Println("\nKey Information:")
fmt.Printf("- Destination (public key): %s...\n", publicKeyPreview)
fmt.Printf("- Full key length: %d characters\n", len(lines[1]))
fmt.Printf("- Full key preview: %s...\n", fullKeyPreview)
fmt.Println("\nFormat: Two lines")
fmt.Println("- Line 1: Base64-encoded destination (public key)")
fmt.Println("- Line 2: Base64-encoded full keypair (public + private)")
}
}
} else {
fmt.Println("Warning: Output file is not in the correct format")
os.Exit(1)
}
}
// truncateString truncates a string and adds ellipsis if needed
func truncateString(s string, maxLen int) string {
if len(s) <= maxLen {
return s
}
return s[:maxLen] + "..."
}