mirror of
https://github.com/go-i2p/i2pkeys-converter.git
synced 2025-06-07 10:01:41 -04:00
add appliation
This commit is contained in:
222
i2pkeys/extractor.go
Normal file
222
i2pkeys/extractor.go
Normal 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
120
main.go
Normal 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] + "..."
|
||||
}
|
Reference in New Issue
Block a user