mirror of
https://github.com/go-i2p/i2pkeys-converter.git
synced 2025-07-19 10:25:40 -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