func saveKey(path string, key *otr.PrivateKey) { var rawKey []byte establishDir(true) rawKey = key.Serialize(rawKey) base64Key := make([]byte, base64.StdEncoding.EncodedLen(len(rawKey))) base64.StdEncoding.Encode(base64Key, rawKey) if err := ioutil.WriteFile(path, base64Key, 0600); err != nil { exitError(err) } }
// ImportFromLibOTR parses the contents of a libotr private key file and imports all the keys found. func ImportFromLibOTR(in []byte) []otr.PrivateKey { acctStart := []byte("(account") ret := []otr.PrivateKey{} var i, p int for { i = bytes.Index(in[p:], acctStart) if i == -1 { break } p += i + len(acctStart) key := otr.PrivateKey{} if key.Import(in[p:]) { ret = append(ret, key) } } return ret }
// Loads and parses a private key. func loadKey(path string) *otr.PrivateKey { establishDir(false) base64Key, err := ioutil.ReadFile(path) if os.IsNotExist(err) { exitPrintf("The private key (%s) does not exist. Please use genkey.\n", path) } if err != nil { exitError(err) } rawKey := make([]byte, base64.StdEncoding.DecodedLen(len(base64Key))) n, err := base64.StdEncoding.Decode(rawKey, base64Key) if err != nil { exitError(err) } key := new(otr.PrivateKey) if _, ok := key.Parse(rawKey[:n]); !ok { exitPrintf("Invalid or corrupted private key (%s).\n", path) } return key }
func enroll(config *Config, term *terminal.Terminal) bool { var err error warn(term, "Enrolling new config file") var domain string for { term.SetPrompt("Account (i.e. [email protected], enter to quit): ") if config.Account, err = term.ReadLine(); err != nil || len(config.Account) == 0 { return false } parts := strings.SplitN(config.Account, "@", 2) if len(parts) != 2 { alert(term, "invalid username (want user@domain): "+config.Account) continue } domain = parts[1] break } term.SetPrompt("Resource name (i.e. work, enter for empty): ") if config.Resource, err = term.ReadLine(); err != nil { return false } const debugLogFile = "/tmp/xmpp-client-debug.log" term.SetPrompt("Enable debug logging to " + debugLogFile + " (y/n)?: ") if debugLog, err := term.ReadLine(); err != nil || !isYes(debugLog) { info(term, "Not enabling debug logging...") } else { config.RawLogFile = debugLogFile info(term, "Debug logging enabled.") } term.SetPrompt("Use Tor (y/n)?: ") if useTorQuery, err := term.ReadLine(); err != nil || !isYes(useTorQuery) { info(term, "Not using Tor...") config.UseTor = false } else { info(term, "Using Tor...") config.UseTor = true } term.SetPrompt("File to import libotr private key from (enter to generate): ") var priv otr.PrivateKey for { importFile, err := term.ReadLine() if err != nil { return false } if len(importFile) > 0 { privKeyBytes, err := ioutil.ReadFile(importFile) if err != nil { alert(term, "Failed to open private key file: "+err.Error()) continue } if !priv.Import(privKeyBytes) { alert(term, "Failed to parse libotr private key file (the parser is pretty simple I'm afraid)") continue } break } else { info(term, "Generating private key...") priv.Generate(rand.Reader) break } } config.PrivateKey = priv.Serialize(nil) config.OTRAutoAppendTag = true config.OTRAutoStartSession = true config.OTRAutoTearDown = false // List well known Tor hidden services. knownTorDomain := map[string]string{ "jabber.ccc.de": "okj7xc6j2szr2y75.onion", "riseup.net": "4cjw6cwpeaeppfqz.onion", "jabber.calyxinstitute.org": "ijeeynrc6x2uy5ob.onion", "jabber.otr.im": "5rgdtlawqkcplz75.onion", "wtfismyip.com": "ofkztxcohimx34la.onion", "rows.io": "yz6yiv2hxyagvwy6.onion", } // Autoconfigure well known Tor hidden services. if hiddenService, ok := knownTorDomain[domain]; ok && config.UseTor { const torProxyURL = "socks5://127.0.0.1:9050" info(term, "It appears that you are using a well known server and we will use its Tor hidden service to connect.") config.Server = hiddenService config.Port = 5222 config.Proxies = []string{torProxyURL} term.SetPrompt("> ") return true } var proxyStr string proxyDefaultPrompt := ", enter for none" if config.UseTor { proxyDefaultPrompt = ", which is the default" } term.SetPrompt("Proxy (i.e socks5://127.0.0.1:9050" + proxyDefaultPrompt + "): ") for { if proxyStr, err = term.ReadLine(); err != nil { return false } if len(proxyStr) == 0 { if !config.UseTor { break } else { proxyStr = "socks5://127.0.0.1:9050" } } u, err := url.Parse(proxyStr) if err != nil { alert(term, "Failed to parse "+proxyStr+" as a URL: "+err.Error()) continue } if _, err = proxy.FromURL(u, proxy.Direct); err != nil { alert(term, "Failed to parse "+proxyStr+" as a proxy: "+err.Error()) continue } break } if len(proxyStr) > 0 { config.Proxies = []string{proxyStr} info(term, "Since you selected a proxy, we need to know the server and port to connect to as a SRV lookup would leak information every time.") term.SetPrompt("Server (i.e. xmpp.example.com, enter to lookup using unproxied DNS): ") if config.Server, err = term.ReadLine(); err != nil { return false } if len(config.Server) == 0 { var port uint16 info(term, "Performing SRV lookup") if config.Server, port, err = xmpp.Resolve(domain); err != nil { alert(term, "SRV lookup failed: "+err.Error()) return false } config.Port = int(port) info(term, "Resolved "+config.Server+":"+strconv.Itoa(config.Port)) } else { for { term.SetPrompt("Port (enter for 5222): ") portStr, err := term.ReadLine() if err != nil { return false } if len(portStr) == 0 { portStr = "5222" } if config.Port, err = strconv.Atoi(portStr); err != nil || config.Port <= 0 || config.Port > 65535 { info(term, "Port numbers must be 0 < port <= 65535") continue } break } } } term.SetPrompt("> ") return true }
func main() { flag.Parse() log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile) prv := otr.PrivateKey{} if *genKey { prv.Generate(rand.Reader) fmt.Println(base64.RawStdEncoding.EncodeToString(prv.Serialize(nil))) return } if *prvDataEnc == "" { log.Fatalln("You have to provide OTR key") } prvData, err := base64.RawStdEncoding.DecodeString(*prvDataEnc) if err != nil { log.Fatalln("Invalid private key's Base64 encoding:", err) } if _, ok := prv.Parse(prvData); !ok { log.Fatalln("Unable to parse private key") } log.Println("Our fingerprint:", hex.EncodeToString(prv.Fingerprint())) var wg sync.WaitGroup var ctin *os.File var ctout *os.File var ptin *os.File var ptout *os.File opener := func(fdd **os.File, path string, flag int) { fd, err := os.OpenFile(path, flag, os.FileMode(0600)) if err != nil { log.Fatalln("Unable to open", path, "file:", err) } *fdd = fd wg.Done() } wg.Add(1) go opener(&ctin, *ctInPath, os.O_RDONLY) wg.Add(1) go opener(&ptin, *ptInPath, os.O_RDONLY) wg.Add(1) go opener(&ctout, *ctOutPath, os.O_WRONLY) wg.Add(1) go opener(&ptout, *ptOutPath, os.O_WRONLY) wg.Wait() conv := otr.Conversation{PrivateKey: &prv, FragmentSize: MaxMsgSize} exited := make(chan struct{}) go func() { scanner := bufio.NewScanner(ptin) var err error var msg []byte var msgs [][]byte var t string for scanner.Scan() { t = scanner.Text() if strings.HasPrefix(t, OTRStart) { ctout.Write(append([]byte(otr.QueryMessage), '\n')) continue } else if t == OTRStop { msgs = conv.End() } else if strings.HasPrefix(t, OTRSMP) { ss := strings.SplitN(t, " ", 3) var question string if len(ss) == 1 { log.Println("Invalid SMP usage") continue } else if len(ss) == 3 { question = ss[2] } msgs, err = conv.Authenticate(question, []byte(ss[1])) if err != nil { log.Println("Error during authentication:", err) continue } } else { msgs, err = conv.Send(scanner.Bytes()) if err != nil { log.Println("Error during sending:", err) continue } } for _, msg = range msgs { ctout.Write(append(msg, '\n')) } } exited <- struct{}{} }() go func() { scanner := bufio.NewScanner(ctin) var got []byte var msg []byte var enc bool var change otr.SecurityChange var toSend [][]byte var err error for scanner.Scan() { got, enc, change, toSend, err = conv.Receive(scanner.Bytes()) if err != nil { log.Println("Error during receiving:", err) continue } for _, msg = range toSend { ctout.Write(append(msg, '\n')) } switch change { case otr.NewKeys: log.Println( "OTR established, remote fingerprint:", hex.EncodeToString(conv.TheirPublicKey.Fingerprint()), ) case otr.ConversationEnded: log.Println("OTR terminated") case otr.SMPSecretNeeded: log.Println("SMP requested:", conv.SMPQuestion()) case otr.SMPComplete: log.Println("SMP succeeded") case otr.SMPFailed: log.Println("SMP failed") } if len(got) > 0 { if !enc { got = append([]byte("Unencrypted:"), got...) } ptout.Write(append(got, '\n')) } } exited <- struct{}{} }() killed := make(chan os.Signal) signal.Notify(killed, os.Interrupt, os.Kill) select { case <-exited: case <-killed: } if conv.IsEncrypted() { for _, msg := range conv.End() { ctout.Write(append(msg, '\n')) } } ctout.Close() ctin.Close() ptout.Close() ptin.Close() }