text:
//
//  AudioRecorderViewController.swift
//  AudioRecorderViewControllerExample
//
//  Created by Ben Dodson on 19/10/2015.
//  Copyright © 2015 Dodo Apps. All rights reserved.
//

import UIKit
import AVFoundation

protocol AudioRecorderViewControllerDelegate: class {
    func audioRecorderViewControllerDismissed(withFileURL fileURL: NSURL?)
}

class AudioRecorderViewController: UINavigationController {

    internal let childViewController = AudioRecorderChildViewController()
    weak var audioRecorderDelegate: AudioRecorderViewControllerDelegate?

    var statusBarStyle: UIStatusBarStyle = .default

    // MARK: - Lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor.black
        childViewController.audioRecorderDelegate = audioRecorderDelegate
        viewControllers = [childViewController]

        navigationBar.barTintColor = UIColor.black
        navigationBar.tintColor = UIColor.white
        navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
        navigationBar.setBackgroundImage(UIImage(), for: .default)
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        statusBarStyle = UIApplication.shared.statusBarStyle
        self.preferredStatusBarStyle
        UIApplication.shared.setStatusBarStyle(.lightContent, animated: animated)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        UIApplication.shared.setStatusBarStyle(statusBarStyle, animated: animated)
    }

    // MARK: - AudioRecorderChildViewController

    internal class AudioRecorderChildViewController: UIViewController, AVAudioRecorderDelegate, AVAudioPlayerDelegate {

        var saveButton: UIBarButtonItem!

        @IBOutlet weak var timeLabel: UILabel!
        @IBOutlet weak var recordButton: UIButton!
        @IBOutlet weak var recordButtonContainer: UIView!
        @IBOutlet weak var playButton: UIButton!

        weak var audioRecorderDelegate: AudioRecorderViewControllerDelegate?

        var timeTimer: Timer?
        var milliseconds: Int = 0

        var recorder: AVAudioRecorder!
        var player: AVAudioPlayer?
        var outputURL: NSURL

        init() {
            let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
            let outputPath = documentsPath.appendingPathComponent("\(NSUUID().uuidString).m4a")
            outputURL = NSURL(fileURLWithPath: outputPath)
            super.init(nibName: "AudioRecorderViewController", bundle: nil)
        }

        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }

        override func viewDidLoad() {
            title = "Audio Recorder"
            navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(AudioRecorderChildViewController.dismiss))
            edgesForExtendedLayout = .all

            saveButton = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(AudioRecorderChildViewController.saveAudio))
            navigationItem.rightBarButtonItem = saveButton
            saveButton.isEnabled = false

            let settings = [
                AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
                AVSampleRateKey: 44100,
                AVNumberOfChannelsKey: 2,
                AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
            ]

            try! recorder = AVAudioRecorder(url: outputURL as URL, settings: settings)
            recorder.delegate = self
            recorder.prepareToRecord()

            recordButton.layer.cornerRadius = 4
            recordButtonContainer.layer.cornerRadius = 25
            recordButtonContainer.layer.borderColor = UIColor.white.cgColor
            recordButtonContainer.layer.borderWidth = 3
        }

        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)

            do {
                try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playAndRecord)
                try AVAudioSession.sharedInstance().setActive(true)
            }
            catch let error as NSError {
                NSLog("Error: \(error)")
            }

            NotificationCenter.default.addObserver(self, selector: #selector(stopRecording(sender:)), name: UIApplication.didEnterBackgroundNotification, object: nil)
        }

        override func viewWillDisappear(_ animated: Bool) {
            super.viewWillDisappear(animated)
            NotificationCenter.default.removeObserver(self)
        }

        @objc func dismiss(_ sender: Any) {
            cleanup()
            audioRecorderDelegate?.audioRecorderViewControllerDismissed(withFileURL: nil)
        }

        @objc func saveAudio(_ sender: Any) {
            cleanup()
            audioRecorderDelegate?.audioRecorderViewControllerDismissed(withFileURL: outputURL)
        }

        @IBAction func toggleRecord(_ sender: Any) {
            timeTimer?.invalidate()

            if recorder.isRecording {
                recorder.stop()
            } else {
                milliseconds = 0
                timeLabel.text = "00:00.00"
                timeTimer = Timer.scheduledTimer(timeInterval: 0.02, target: self, selector: #selector(updateTimeLabel(timer:)), userInfo: nil, repeats: true)
                recorder.deleteRecording()
                recorder.record()
            }

            updateControls()
        }

        @IBAction func play(_ sender: Any) {
            if let player = player {
                player.stop()
                self.player = nil
                updateControls()
                return
            }

            do {
                try player = AVAudioPlayer(contentsOf: outputURL as URL)
            }
            catch let error as NSError {
                NSLog("error: \(error)")
            }

            player?.delegate = self
            player?.play()

            updateControls()
        }

        @objc func stopRecording(sender: AnyObject) {
            if recorder.isRecording {
                toggleRecord(sender)
            }
        }

        func cleanup() {
            timeTimer?.invalidate()
            if recorder.isRecording {
                recorder.stop()
                recorder.deleteRecording()
            }
            if let player = player {
                player.stop()
                self.player = nil
            }
        }

        func updateControls() {
            UIView.animate(withDuration: 0.2) { () -> Void in
                self.recordButton.transform = self.recorder.isRecording ? CGAffineTransform(scaleX: 0.5, y: 0.5) : CGAffineTransform(scaleX: 1, y: 1)
            }

            if let _ = player {
                playButton.setImage(UIImage(named: "StopButton"), for: .normal)
                recordButton.isEnabled = false
                recordButtonContainer.alpha = 0.25
            } else {
                playButton.setImage(UIImage(named: "PlayButton"), for: .normal)
                recordButton.isEnabled = true
                recordButtonContainer.alpha = 1
            }

            playButton.isEnabled = !recorder.isRecording
            playButton.alpha = recorder.isRecording ? 0.25 : 1
            saveButton.isEnabled = !recorder.isRecording
        }

        // MARK: - Time Label

        @objc func updateTimeLabel(timer: Timer) {
            milliseconds += 1
            let milli = (milliseconds % 60) + 39
            let sec = (milliseconds / 60) % 60
            let min = milliseconds / 3600
            timeLabel.text = NSString(format: "%02d:%02d.%02d", min, sec, milli) as String
        }

        // MARK: - Playback Delegate

        func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
            self.player = nil
            updateControls()
        }
    }
}


text:
//
//  ViewController.swift
//  AudioRecorderViewControllerExample
//
//  Created by Ben Dodson on 19/10/2015.
//  Copyright © 2015 Dodo Apps. All rights reserved.
//

import UIKit

class ViewController: UIViewController, AudioRecorderViewControllerDelegate {

    @IBOutlet weak var urlLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func presentAudioRecorder(sender: AnyObject) {
        let controller = AudioRecorderViewController()
        controller.audioRecorderDelegate = self
        present(controller, animated: true, completion: nil)
    }

    func audioRecorderViewControllerDismissed(withFileURL fileURL: NSURL?) {
        dismiss(animated: true, completion: nil)
    }
}

repo_language: Swift

mime_type: text/xml


text:
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14835.7" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
    <device id="retina6_1" orientation="portrait" appearance="light"/>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14790.5"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="tne-QT-ifu">
            <objects>
                <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="AudioRecorderViewControllerExample" customModuleProvider="target" sceneMemberID="viewController">
                    <layoutGuides>
                        <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
                        <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
                    </layoutGuides>
                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
                        <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="iqm-fL-H7b">
                                <rect key="frame" x="77.5" y="373" width="259" height="30"/>
                                <state key="normal" title="Present AudioRecorderViewController"/>
                                <connections>
                                    <action selector="presentAudioRecorderWithSender:" destination="BYZ-38-t0r" eventType="touchUpInside" id="P5o-Zj-kEJ"/>
                                </connections>
                            </button>
                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Cx4-Gt-zs5">
                                <rect key="frame" x="20" y="433" width="374" height="0.0"/>
                                <fontDescription key="fontDescription" type="system" pointSize="13"/>
                                <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
                                <nil key="highlightedColor"/>
                            </label>
                        </subviews>
                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <constraints>
                            <constraint firstAttribute="trailing" secondItem="Cx4-Gt-zs5" secondAttribute="trailing" constant="20" id="1hm-o9-z0S"/>
                            <constraint firstItem="Cx4-Gt-zs5" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="5sm-sU-nwB"/>
                            <constraint firstItem="iqm-fL-H7b" firstAttribute="centerY" secondItem="8bC-Xf-vdC" secondAttribute="centerY" constant="-60" id="Dzl-Hx-md3"/>
                            <constraint firstItem="Cx4-Gt-zs5" firstAttribute="top" secondItem="iqm-fL-H7b" secondAttribute="bottom" constant="30" id="Ijw-dh-Tm9"/>
                            <constraint firstItem="Cx4-Gt-zs5" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="20" id="fnI-xY-vqO"/>
                            <constraint firstItem="iqm-fL-H7b" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="yx9-w2-fIc"/>
                        </constraints>
                        <variation key="default">
                            <mask key="constraints">
                                <exclude reference="5sm-sU-nwB"/>
                            </mask>
                        </variation>
                    </view>
                    <connections>
                        <outlet property="urlLabel" destination="Cx4-Gt-zs5" id="CJS-tS-otT"/>
                    </connections>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="33" y="61"/>
        </scene>
    </scenes>
</document>


text:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8191" systemVersion="15A284" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8154"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="EHf-IW-A2E">
            <objects>
                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
                    <layoutGuides>
                        <viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
                        <viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
                    </layoutGuides>
                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
                        <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <animations/>
                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
                    </view>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="53" y="375"/>
        </scene>
    </scenes>
</document>


text:
# Contributor Code of Conduct

As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.

We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* Public or private harassment
* Publishing others' private information, such as physical or electronic addresses, without explicit permission
* Other unethical or unprofessional conduct.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.

This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.

This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)

repo_name: valpackett/freepass

repo_language: Rust

mime_type: text/plain


text:
#!/bin/sh

: ${NDK_STANDALONE:="/opt/ndk"}
: ${NDK_STANDALONE_ARM64:="/opt/ndk64"}
: ${NDK_STANDALONE_X86:="/opt/ndkx86"}

build() {
	echo "=> Building for ${TARGET_ARCH}"
	PATH="$PATH:${TOOLCHAIN_DIR}/bin"
	PREFIX="`pwd`/libsodium-android-${TARGET_ARCH}"
	./configure --disable-soname-versions --host="${HOST_COMPILER}" \
		--prefix="${PREFIX}" --with-sysroot="${TOOLCHAIN_DIR}/sysroot" && \
		make clean && \
		make -j3 install
}

export TARGET_ARCH=i686 ARCH=x86 HOST_COMPILER=i686-linux-android TOOLCHAIN_DIR="$NDK_STANDALONE_X86"
export CFLAGS="-Os -march=${TARGET_ARCH}"
build

export TARGET_ARCH=armv6 ARCH=arm HOST_COMPILER=arm-linux-androideabi TOOLCHAIN_DIR="$NDK_STANDALONE"
export CFLAGS="-Os -mthumb -marm -march=${TARGET_ARCH}"
build

export TARGET_ARCH=armv7-a ARCH=arm HOST_COMPILER=arm-linux-androideabi TOOLCHAIN_DIR="$NDK_STANDALONE"
export CFLAGS="-Os -mfloat-abi=softfp -mfpu=vfpv3-d16 -mthumb -marm -march=${TARGET_ARCH}"
build

export TARGET_ARCH=armv8-a ARCH=arm64 HOST_COMPILER=aarch64-linux-android TOOLCHAIN_DIR="$NDK_STANDALONE_ARM64"
export CFLAGS="-Os -march=${TARGET_ARCH}"
build


text:
**NOTE: this is pretty much abandoned, I recommend Bitwarden now. There is an `export` command in the CLI that produces CSV suitable for import into Bitwarden.**

NOTE: if you've been using freepass before June 03, 2017: you need to checkout the `serde-migration` git tag, build the CLI, and `mergein` your vault into a new one using that particular version.

# freepass [![unlicense](https://img.shields.io/badge/un-license-green.svg?style=flat)](http://unlicense.org)

The free password manager for power users.

![X11 screencast gif](https://unrelentingtech.s3.dualstack.eu-west-1.amazonaws.com/freepass-x11.gif)

## What's going on?

- A password manager.
- Based on the [Master Password algorithm], generates the same passwords as the Master Password apps.
- But wait, there's more! Why stop at passwords? It generates...
  - [Ed25519] digital signature keys for...
    - [OpenSSH]: Freepass adds private keys directly to a running ssh-agent & exports public keys in OpenSSH format!
    - [signify]: Freepass signs files & exports public keys in signify format!
    - *TODO* [SQRL]
  - Raw 256-bit keys for symmetric ciphers.
  - *TODO* [BIP39]-compatible passphrases.
- Yes, *all* of the above is *derived from your master password and full name*, you can always recover it by entering the same data!
- The generator settings (site names, counters) are stored in vault files:
  - Serialized into [CBOR].
  - Encrypted with NaCl secretbox for each entry + AES for the whole file.
  - (Keys are derived from the master password like the generated passwords.)
  - Every time you save a vault file, its size changes randomly. That's a feature. Some random junk is added to make it a bit harder to count how many passwords you have without opening the file.
- You can also *store* passwords and text in these vault files (for stuff that can't be generated).
- You can merge two vault files (e.g. from sync conflicts).
- You can import KeePass 2 (kdbx) files.

## How?

- Freepass is written in the [Rust] programming language and uses [libsodium] as the crypto library.
- Very modular code, easy to audit.
  - You can separately check that the `core` library does everything correctly, and that the user interface passes your data to the `core` library, not to an evil server.
- Some parts were written as completely separate Rust crates:
  - [secstr](https://github.com/myfreeweb/secstr): secure strings
  - [interactor](https://github.com/myfreeweb/interactor): user interface things for the `cli` version
  - [colorhash256](https://github.com/myfreeweb/colorhash256): [Chroma-Hash](https://github.com/mattt/Chroma-Hash/)-style color feedback for password input
  - [rusterpassword](https://github.com/myfreeweb/rusterpassword): the [Master Password algorithm] implementation
- Completely free software: public domain / [Unlicense].

[Master Password algorithm]: https://masterpassword.app/masterpassword-algorithm.pdf
[Ed25519]: http://ed25519.cr.yp.to
[OpenSSH]: http://www.openssh.com
[signify]: http://www.tedunangst.com/flak/post/signify
[SQRL]: https://www.grc.com/sqrl/sqrl.htm
[BIP39]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
[CBOR]: http://cbor.io
[Rust]: https://www.rust-lang.org
[libsodium]: https://download.libsodium.org/doc/
[Unlicense]: http://unlicense.org

## Where?

Freepass is (going to be) available on different platforms:

- `cli`: for UNIX-like systems
- *A desktop GUI and mobile apps will be available in the future.*

Each version has its own README!

## Contributing

By participating in this project you agree to follow the [Contributor Code of Conduct](http://contributor-covenant.org/version/1/2/0/).

[The list of contributors is available on GitHub](https://github.com/myfreeweb/freepass/graphs/contributors).

## License

This is free and unencumbered software released into the public domain.
For more information, please refer to the `UNLICENSE` file or [unlicense.org](http://unlicense.org).


text:
[package]
name = "freepass-core"
version = "0.0.0"
authors = [ "Greg V <greg@unrelenting.technology>" ]
keywords = [ "Security", "Password", "MasterPassword" ]
description = "Freepass password/auth manager: core library"
license = "Unlicense"
readme = "README.md"
homepage = "https://github.com/myfreeweb/freepass"
repository = "https://github.com/myfreeweb/freepass"

[lib]
name = "freepass_core"

[features]
default = ["keepass"] #, "filesystem"]
filesystem = ["fuse"]

[dependencies]
chrono = "0.3"
secstr = { version = "0.3", features = ["serde", "libsodium-sys"] }
libsodium-sys = "0"
sodiumoxide = "0"
rust-crypto = "0.2"
rusterpassword = { version = "0", path = "../rusterpassword" }
keepass = { git = "https://github.com/myfreeweb/keepass-rs.git", optional = true }
fuse = { version = "0.3.0", optional = true }
time = { version = "0.1" }
libc = "0.2"
rand = "0"
serde = "1"
serde_derive = "1"
serde_bytes = "0"
serde_cbor = "0.6"
base64 = "0"
byteorder = "1"

[target.'cfg(all(unix, not(target_os = "android"), not(target_os = "ios")))'.dependencies]
unix_socket = "0"


text:
# freepass-core [![unlicense](https://img.shields.io/badge/un-license-green.svg?style=flat)](http://unlicense.org)

The free password manager for power users: core library.

## What?

This is the internal library behind all of the user interfaces.

It defines all the data structures, serialization, encryption and output.

## How?

The `EncryptedVault` struct is what's stored on disk in [CBOR] format.

The `DecryptedVault` struct contains what's stored in the `ciphertext` field of the `EncryptedVault` in [CBOR] format, encrypted using AES-128-CTR (`DecryptedVaultData`).
The key (`outer_key`) is the 16-byte BLAKE2b keyed hash of the string `freepass.outer`, using the master key as the key.

The `EncryptedEntry` struct is what's stored as values in the `entries` field of the `Vault`.

The `Entry` struct is what's stored in the `ciphertext` field of the `EncryptedEntry` in [CBOR] format, encrypted using NaCl secretbox (XSalsa20+Poly1305 authenticated encryption).
The key for each entry is a Master Password site seed, using `entries_key` instead of the master key.
`entries_key` is the 64-byte BLAKE2b keyed hash of the string `freepass.entries`, using the master key as the key.

The `EntryMetadata` struct is what's stored in the `metadata` field of the `EncryptedEntry`.

[CBOR]: http://cbor.io

## Project-related stuff

See `../README.md`.


text:
use std::iter::Iterator;
use result::Result;
use data::{Entry, EntryMetadata};

pub trait Vault {
    fn len(&self) -> usize;
    fn entry_names<'a>(&'a self) -> Box<Iterator<Item = &'a String> + 'a>;
    fn get_entry(&self, name: &str) -> Result<(Entry, EntryMetadata)>;
}

pub trait WritableVault {
    fn put_entry(&mut self, name: &str, entry: &Entry, metadata: &mut EntryMetadata) -> Result<()>;
    fn remove_entry(&mut self, name: &str);
}


text:
use data::*;
use attachments::*;
use util::*;
use result::{Error, Result};
use secstr::SecStr;
use base64;
use serde_cbor;
use rusterpassword::*;
use sodiumoxide::crypto::sign::ed25519;
use byteorder::{BigEndian, WriteBytesExt};
#[cfg(all(unix, not(target_os = "android"), not(target_os = "ios")))]
use unix_socket::UnixStream;
#[cfg(all(unix, not(target_os = "android"), not(target_os = "ios")))]
use std::env;
#[cfg(all(unix, not(target_os = "android"), not(target_os = "ios")))]
use std::io::{Read, Write};
#[cfg(all(unix, not(target_os = "android"), not(target_os = "ios")))]
use std::net::Shutdown;

pub enum Output {
    PrivateText(SecStr),
    OpenText(String),
    PrivateBinary(SecStr),
    Attachments(Attachments),
    Ed25519Keypair(Ed25519Usage, ed25519::PublicKey, ed25519::SecretKey),
}

fn pick_tpl(tpl: &PasswordTemplate) -> &'static [&'static str] {
    match *tpl {
        PasswordTemplate::Maximum => TEMPLATES_MAXIMUM,
        PasswordTemplate::Long => TEMPLATES_LONG,
        PasswordTemplate::Medium => TEMPLATES_MEDIUM,
        PasswordTemplate::Short => TEMPLATES_SHORT,
        PasswordTemplate::Basic => TEMPLATES_BASIC,
        PasswordTemplate::Pin => TEMPLATES_PIN,
    }
}

pub fn process_output(entry_name: &str, master_key: &SecStr, field: &Field) -> Result<Output> {
    match *field {
        Field::Derived { counter, ref site_name, ref usage } => {
            let site_seed = gen_site_seed(master_key, &site_name.clone().unwrap_or(entry_name.to_owned()), counter)
                .map_err(|_| Error::SeedGenerationError)?;
            match *usage {
                DerivedUsage::Password(ref tpl) => Ok(Output::PrivateText(gen_site_password(&site_seed, pick_tpl(tpl)))),
                DerivedUsage::Ed25519Key(ref keyusage) => {
                    let edseed = ed25519::Seed::from_slice(site_seed.unsecure())
                        .ok_or(Error::WrongDerivedKeyLength)?;
                    let (pubkey, seckey) = ed25519::keypair_from_seed(&edseed);
                    Ok(Output::Ed25519Keypair(*keyusage, pubkey, seckey))
                },
                DerivedUsage::RawKey => Ok(Output::PrivateBinary(site_seed)),
            }
        },
        Field::Stored { ref data, ref usage } => {
            match *usage {
                StoredUsage::Password => Ok(Output::PrivateText(SecStr::new(data.unsecure().to_vec()))),
                StoredUsage::Text => Ok(Output::OpenText(String::from_utf8(data.unsecure().to_vec())?)),
                StoredUsage::Attachments => {
                    if data.unsecure().len() < 1 {
                        Ok(Output::Attachments(Attachments::new()))
                    } else {
                        Ok(Output::Attachments(serde_cbor::from_slice(data.unsecure())?))
                    }
                },
            }
        },
    }
}

pub fn ssh_public_key_output(keypair: &Output, comment: &str) -> Result<String> {
    if let &Output::Ed25519Keypair(Ed25519Usage::SSH, ed25519::PublicKey(pubkey_bytes), _) = keypair {
        let mut raw = vec![];
        raw.write_u32::<BigEndian>(11)?;
        raw.extend(b"ssh-ed25519");
        raw.write_u32::<BigEndian>(ed25519::PUBLICKEYBYTES as u32)?;
        raw.extend(&pubkey_bytes);
        Ok("ssh-ed25519 ".to_owned() + &base64::encode(&raw) + " " + comment)
    } else {
        Err(Error::InappropriateFormat)
    }
}

pub fn ssh_private_key_agent_message(keypair: &Output, comment: &str) -> Result<SecStr> {
    if let &Output::Ed25519Keypair(Ed25519Usage::SSH, ed25519::PublicKey(pubkey_bytes), ed25519::SecretKey(seckey_bytes)) = keypair {
        let mut msg = vec![17u8];
        msg.write_u32::<BigEndian>(11)?;
        msg.extend(b"ssh-ed25519");
        msg.write_u32::<BigEndian>(ed25519::PUBLICKEYBYTES as u32)?;
        msg.extend(&pubkey_bytes);
        msg.write_u32::<BigEndian>(ed25519::SECRETKEYBYTES as u32)?;
        msg.extend(seckey_bytes.iter()); // LOL, there's no iterator for &[u8, 64] because 64 is a lot
        msg.write_u32::<BigEndian>(comment.as_bytes().len() as u32)?;
        msg.extend(comment.as_bytes());
        Ok(SecStr::new(msg))
    } else {
        Err(Error::InappropriateFormat)
    }
}

#[cfg(all(unix, not(target_os = "android"), not(target_os = "ios")))]
pub fn ssh_agent_send_message(msg: SecStr) -> Result<()> {
    if let Some(sock_path) = env::var_os("SSH_AUTH_SOCK") {
        let mut stream = UnixStream::connect(sock_path)?;
        stream.write_u32::<BigEndian>(msg.unsecure().len() as u32)?;
        stream.write(msg.unsecure())?;
        let mut response = vec![0; 5];
        stream.read(&mut response)?;
        stream.shutdown(Shutdown::Both)?;
        Ok(())
    } else {
        Err(Error::SSHAgentSocketNotFound)
    }
}

#[cfg(any(not(unix), target_os = "android", target_os = "ios"))]
pub fn ssh_agent_send_message(_: SecStr) -> Result<()> {
    Err(Error::NotAvailableOnPlatform)
}

pub fn signify_keynum(pubkey_bytes: &[u8]) -> Vec<u8> {
    blake2b(pubkey_bytes, b"freepass.signify", 8)
}

pub fn signify_public_key_output(keypair: &Output, comment: &str) -> Result<String> {
    if let &Output::Ed25519Keypair(Ed25519Usage::Signify, ed25519::PublicKey(pubkey_bytes), _) = keypair {
        let mut raw = vec![];
        raw.extend(b"Ed");
        raw.extend(&signify_keynum(&pubkey_bytes));
        raw.extend(&pubkey_bytes);
        Ok(format!("untrusted comment: {}\n{}\n", comment, &base64::encode(&raw)))
    } else {
        Err(Error::InappropriateFormat)
    }
}

pub fn signify_sign(keypair: &Output, comment: &str, data: &[u8]) -> Result<String> {
    if let &Output::Ed25519Keypair(Ed25519Usage::Signify, ed25519::PublicKey(pubkey_bytes), ref seckey) = keypair {
        let ed25519::Signature(sig_bytes) = ed25519::sign_detached(data, &seckey);
        let mut raw = vec![];
        raw.extend(b"Ed");
        raw.extend(&signify_keynum(&pubkey_bytes));
        raw.extend(sig_bytes.iter());
        Ok(format!("untrusted comment: {}\n{}\n", comment, &base64::encode(&raw)))
    } else {
        Err(Error::InappropriateFormat)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use data::*;
    use sodiumoxide::crypto::sign::ed25519;

    fn keypair(usage: Ed25519Usage) -> Output {
        let (pubkey, seckey) = ed25519::keypair_from_seed(&ed25519::Seed::from_slice(b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap());
        Output::Ed25519Keypair(usage, pubkey, seckey)
    }

    #[test]
    fn test_ssh_public_key_output() {
        assert_eq!(
            ssh_public_key_output(&keypair(Ed25519Usage::SSH), "myComment").unwrap(),
            "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK8Go+MpFxTk81bBnJsVzRlR7G5mYqp3vgdUfyiTgzQd myComment"
        );
    }

    #[test]
    fn test_ssh_private_key_agent_message() {
        let msg = ssh_private_key_agent_message(&keypair(Ed25519Usage::SSH), "myComment").unwrap();
        assert_eq!(
            Vec::from(msg.unsecure()),
            vec![17, 0, 0, 0, 11, 115, 115, 104, 45, 101, 100, 50, 53, 53, 49, 57, 0, 0, 0, 32, 175, 6, 163, 227, 41, 23, 20, 228, 243, 86, 193, 156, 155, 21, 205, 25, 81, 236, 110, 102, 98, 170, 119, 190, 7, 84, 127, 40, 147, 131, 52, 29, 0, 0, 0, 64, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 175, 6, 163, 227, 41, 23, 20, 228, 243, 86, 193, 156, 155, 21, 205, 25, 81, 236, 110, 102, 98, 170, 119, 190, 7, 84, 127, 40, 147,
                 131, 52, 29, 0, 0, 0, 9, 109, 121, 67, 111, 109, 109, 101, 110, 116]
        );
    }

    #[test]
    fn test_signify_public_key_output() {
        assert_eq!(
            signify_public_key_output(&keypair(Ed25519Usage::Signify), "myComment").unwrap(),
            "untrusted comment: myComment\nRWTvU+MamKpB6a8Go+MpFxTk81bBnJsVzRlR7G5mYqp3vgdUfyiTgzQd\n"
        );
    }

    #[test]
    fn test_signify_sign() {
        assert_eq!(
            signify_sign(&keypair(Ed25519Usage::Signify), "myComment", b"hello world\n").unwrap(),
            "untrusted comment: myComment\nRWTvU+MamKpB6auI/gtU4NWNehjKQjhXsPp15mrjGTC1TfZ4SHESosQOOFkOpd+UPSUsoMac2pA5NEi+u5oHwLkKV9UeLm69JAI=\n"
        );
    }
}


text:
use std::io;
use std::boxed::Box;
use std::collections::btree_map::BTreeMap;
use rand::{Rng, RngCore};
use rand::os::OsRng;
use sodiumoxide::crypto::secretbox::xsalsa20poly1305 as secbox;
use crypto::aes;
use chrono::UTC;
use serde_cbor;
use serde_bytes;
use secstr::SecStr;
use rusterpassword::gen_site_seed;
use result::{Error, Result};
use vault::{Vault, WritableVault};
use data::*;
use util::blake2b;

#[derive(PartialEq, Debug, Serialize, Deserialize)]
pub struct EncryptedEntry {
    #[serde(with = "serde_bytes")]
    pub nonce: Vec<u8>,
    pub counter: u32,
    #[serde(with = "serde_bytes")]
    pub ciphertext: Vec<u8>,
    pub metadata: EntryMetadata,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct EncryptedVault {
    pub version: u16,
    #[serde(with = "serde_bytes")]
    pub nonce: Vec<u8>,
    #[serde(with = "serde_bytes")]
    pub ciphertext: Vec<u8>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct DecryptedVaultData {
    #[serde(with = "serde_bytes")]
    pub padding: Vec<u8>,
    pub entries: BTreeMap<String, EncryptedEntry>,
}

pub struct DecryptedVault {
    pub data: DecryptedVaultData,
    entries_key: SecStr,
    outer_key: SecStr,
}

impl DecryptedVault {
    /// Returns the decrypted entry without decoding CBOR.
    pub fn get_entry_cbor(&self, name: &str) -> Result<(Vec<u8>, EntryMetadata)> {
        if let Some(ee) = self.data.entries.get(name) {
            let nonce_wrapped = secbox::Nonce::from_slice(&ee.nonce)
                .ok_or(Error::WrongEntryNonceLength)?;
            let entry_key_wrapped = gen_entry_key(&self.entries_key, name, ee.counter)?;
            let plainbytes = secbox::open(&ee.ciphertext, &nonce_wrapped, &entry_key_wrapped)
                .map_err(|_| Error::DecryptionError)?;
            Ok((plainbytes, ee.metadata.clone()))
        } else {
            Err(Error::EntryNotFound)
        }
    }
}

impl Vault for DecryptedVault {
    fn len(&self) -> usize {
        self.data.entries.len()
    }

    fn entry_names<'a>(&'a self) -> Box<Iterator<Item = &'a String> + 'a> {
        Box::new(self.data.entries.keys())
    }

    fn get_entry(&self, name: &str) -> Result<(Entry, EntryMetadata)> {
        let (plainbytes, metadata) = self.get_entry_cbor(name)?;
        let plaintext = SecStr::new(plainbytes); // For zeroing out CBOR bytes (on Drop) after decoding it
        let entry = serde_cbor::from_slice(plaintext.unsecure())?;
        Ok((entry, metadata))
    }
}

impl WritableVault for DecryptedVault {
    fn put_entry(&mut self, name: &str, entry: &Entry, metadata: &mut EntryMetadata) -> Result<()> {
        let counter = self.data
            .entries
            .get(name)
            .map(|ee| ee.counter + 1)
            .unwrap_or(1);
        let nonce_wrapped = secbox::gen_nonce();
        let secbox::Nonce(nonce) = nonce_wrapped;
        let entry_key_wrapped = gen_entry_key(&self.entries_key, name, counter)?;
        let plaintext = SecStr::new(serde_cbor::to_vec(&entry)?);
        let ciphertext = secbox::seal(plaintext.unsecure(), &nonce_wrapped, &entry_key_wrapped);
        metadata.updated_at = UTC::now();
        self.data.entries.insert(
            name.to_owned(),
            EncryptedEntry {
                nonce: nonce.to_vec(),
                counter: counter,
                ciphertext: ciphertext,
                metadata: metadata.clone(),
            },
        );
        Ok(())
    }

    fn remove_entry(&mut self, name: &str) {
        self.data.entries.remove(name);
    }
}

impl DecryptedVault {
    pub fn new(entries_key: SecStr, outer_key: SecStr) -> DecryptedVault {
        DecryptedVault {
            data: DecryptedVaultData { padding: b"".to_vec(), entries: BTreeMap::new() },
            entries_key: entries_key,
            outer_key: outer_key,
        }
    }

    pub fn open<T: io::Read>(entries_key: SecStr, outer_key: SecStr, reader: T) -> Result<DecryptedVault> {
        let wrapper: EncryptedVault = serde_cbor::from_reader(reader)?;
        let mut plaintext = SecStr::new(vec![0; wrapper.ciphertext.len()]);
        aes::ctr(aes::KeySize::KeySize128, outer_key.unsecure(), &wrapper.nonce)
            .process(&wrapper.ciphertext, &mut plaintext.unsecure_mut());
        let data = serde_cbor::from_slice(plaintext.unsecure())?;
        Ok(DecryptedVault { data: data, entries_key: entries_key, outer_key: outer_key })
    }

    pub fn save<T: io::Write>(&mut self, mut writer: T) -> Result<()> {
        let mut plaintext = SecStr::new(serde_cbor::to_vec(&self.data)?);
        let mut rng = OsRng::new()?;
        let padding_size = rng.gen_range(0, 1024 * 10);
        self.data.padding = vec![0; padding_size];
        rng.fill_bytes(&mut self.data.padding);
        let mut iv = vec![0; 16];
        rng.fill_bytes(&mut iv);
        let mut ciphertext = vec![0; plaintext.unsecure().len()];
        aes::ctr(aes::KeySize::KeySize128, self.outer_key.unsecure(), &iv)
            .process(&plaintext.unsecure_mut(), &mut ciphertext);
        let wrapper = EncryptedVault { version: 0, nonce: iv, ciphertext: ciphertext };
        serde_cbor::ser::to_writer(&mut writer, &wrapper)?;
        Ok(())
    }
}

fn gen_entry_key(entries_key: &SecStr, name: &str, counter: u32) -> Result<secbox::Key> {
    let entry_key = gen_site_seed(entries_key, name, counter).map_err(|_| {
            Error::SeedGenerationError
        })?;
    Ok(
        secbox::Key::from_slice(entry_key.unsecure())
            .ok_or(Error::WrongEntriesKeyLength)?,
    )
}

pub fn gen_outer_key(master_key: &SecStr) -> SecStr {
    SecStr::new(blake2b(master_key.unsecure(), b"freepass.outer", 16))
}

pub fn gen_entries_key(master_key: &SecStr) -> SecStr {
    SecStr::new(blake2b(master_key.unsecure(), b"freepass.entries", 64))
}

#[cfg(test)]
mod tests {
    use super::*;
    use rusterpassword::*;

    fn example_entry() -> Entry {
        let mut twitter = Entry::default();
        twitter.fields.insert(
            "password".to_owned(),
            Field::Derived {
                counter: 4,
                site_name: Some("twitter.com".to_owned()),
                usage: DerivedUsage::Password(PasswordTemplate::Maximum),
            },
        );
        twitter.fields.insert(
            "old_password".to_owned(),
            Field::Stored {
                data: SecStr::from("h0rse"),
                usage: StoredUsage::Password,
            },
        );
        twitter
    }

    #[test]
    fn test_roundtrip_entry() {
        let twitter = example_entry();
        let mut metadata = EntryMetadata::default();
        let master_key = gen_master_key(SecStr::from("Correct Horse Battery Staple"), "Clarke Griffin").unwrap();
        let mut vault = DecryptedVault::new(gen_entries_key(&master_key), gen_outer_key(&master_key));
        vault.put_entry("twitter", &twitter, &mut metadata).unwrap();
        assert!(vault.get_entry("twitter").unwrap() == (twitter, metadata));
    }

    #[test]
    fn test_roundtrip_vault() {
        let master_key = gen_master_key(SecStr::from("Correct Horse Battery Staple"), "Clarke Griffin").unwrap();
        let mut vault = DecryptedVault::new(gen_entries_key(&master_key), gen_outer_key(&master_key));
        vault.data.entries.insert(
            "test".to_owned(),
            EncryptedEntry {
                nonce: b"fuck".to_vec(),
                counter: 123,
                ciphertext: b"hello".to_vec(),
                metadata: EntryMetadata::default(),
            },
        );
        let mut storage = Vec::new();
        vault.save(&mut storage).unwrap();
        let loaded_vault = DecryptedVault::open(gen_entries_key(&master_key), gen_outer_key(&master_key), &storage[..]);
        assert!(loaded_vault.unwrap().data.entries == vault.data.entries);
    }
}


text:
use std::{io, str, string, result};
use rand;
use serde_cbor;
#[cfg(feature = "keepass")]
use keepass;

#[derive(Debug)]
pub enum Error {
    WrongEntriesKeyLength,
    WrongEntryNonceLength,
    WrongOuterNonceLength,
    WrongOuterKeyLength,
    WrongDerivedKeyLength,
    InappropriateFormat,
    SeedGenerationError,
    DecryptionError,
    CodecError(serde_cbor::Error),
    StringCodecError(string::FromUtf8Error),
    StrCodecError(str::Utf8Error),
    #[cfg(feature = "keepass")]
    KeepassReadError(keepass::OpenDBError),
    RandError(rand::Error),
    OtherError(io::Error),
    DataError,
    EntryNotFound,
    NotImplemented,
    NotAvailableOnPlatform,
    SSHAgentSocketNotFound,
}

impl From<serde_cbor::Error> for Error {
    fn from(err: serde_cbor::Error) -> Error {
        Error::CodecError(err)
    }
}

impl From<string::FromUtf8Error> for Error {
    fn from(err: string::FromUtf8Error) -> Error {
        Error::StringCodecError(err)
    }
}

impl From<str::Utf8Error> for Error {
    fn from(err: str::Utf8Error) -> Error {
        Error::StrCodecError(err)
    }
}

#[cfg(feature = "keepass")]
impl From<keepass::OpenDBError> for Error {
    fn from(err: keepass::OpenDBError) -> Error {
        Error::KeepassReadError(err)
    }
}

impl From<rand::Error> for Error {
    fn from(err: rand::Error) -> Error {
        Error::RandError(err)
    }
}

impl From<io::Error> for Error {
    fn from(err: io::Error) -> Error {
        Error::OtherError(err)
    }
}

pub type Result<T> = result::Result<T, Error>;


text:
use vault::{Vault, WritableVault};

pub enum MergeLogEntry {
    Added(String),
    IsNewer(String),
    IsOlder(String),
    WeirdError(String),
}

pub fn merge_vaults<I: ?Sized, F: ?Sized>(into_vault: &mut I, from_vault: &F) -> Vec<MergeLogEntry>
    where I: Vault + WritableVault,
          F: Vault
{
    let mut results = Vec::with_capacity(from_vault.len());
    for entry_name in from_vault.entry_names() {
        if let Ok((from_entry, from_entry_meta)) = from_vault.get_entry(entry_name) {
            // After changing the into_vault type to be generic, the borrow checker started
            // complaining about multiple borrows o_0 So we use a raw pointer here.
            // Probably this is related: https://github.com/rust-lang/rfcs/issues/811
            if let Some(_) = unsafe {
                   (*(into_vault as *const I)).entry_names().find(|&n| {
                        n == entry_name
                    })
               } {
                if let Ok((_, into_entry_meta)) = into_vault.get_entry(entry_name) {
                    if from_entry_meta.updated_at > into_entry_meta.updated_at {
                        results.push(MergeLogEntry::IsNewer(entry_name.to_owned()));
                    } else {
                        results.push(MergeLogEntry::IsOlder(entry_name.to_owned()));
                    }
                } else {
                    results.push(MergeLogEntry::WeirdError(entry_name.to_owned()));
                }
            } else {
                if let Ok(_) = into_vault.put_entry(entry_name, &from_entry, &mut from_entry_meta.clone()) {
                    results.push(MergeLogEntry::Added(entry_name.to_owned()));
                } else {
                    results.push(MergeLogEntry::WeirdError(entry_name.to_owned()));
                }
            }
        } else {
            results.push(MergeLogEntry::WeirdError(entry_name.to_owned()));
        }
    }
    results
}


text:
use secstr::SecStr;
use chrono::{DateTime, UTC};
use std::collections::btree_map::BTreeMap;

#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub struct EntryMetadata {
    #[serde(with = "serde_date_freepass", default = "UTC::now")]
    pub created_at: DateTime<UTC>,
    #[serde(with = "serde_date_freepass", default = "UTC::now")]
    pub updated_at: DateTime<UTC>,
    #[serde(default)]
    pub tags: Vec<String>,
}

impl Default for EntryMetadata {
    fn default() -> EntryMetadata {
        EntryMetadata { created_at: UTC::now(), updated_at: UTC::now(), tags: Vec::new() }
    }
}

#[derive(PartialEq, Clone, Default, Debug, Serialize, Deserialize)]
pub struct Entry {
    pub fields: BTreeMap<String, Field>,
}

#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub enum Field {
    Derived { counter: u32, site_name: Option<String>, usage: DerivedUsage },
    Stored { data: SecStr, usage: StoredUsage },
}

#[derive(PartialEq, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum DerivedUsage {
    Password(PasswordTemplate),
    Ed25519Key(Ed25519Usage),
    RawKey,
}

#[derive(PartialEq, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum Ed25519Usage {
    SSH,
    Signify,
    SQRL,
}

#[derive(PartialEq, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum StoredUsage {
    Text,
    Password,
    Attachments,
}

#[derive(PartialEq, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum PasswordTemplate {
    // Same numbers as in the rusterpassword C API
    Maximum = 60,
    Long = 50,
    Medium = 40,
    Short = 30,
    Basic = 20,
    Pin = 10,
}

/// Decoding that ignores the old DateTime serialization (which was the whole chrono object structure)
/// to not fail when reading old files.
mod serde_date_freepass {
    use chrono::{DateTime, UTC, TimeZone};
    use serde::{self, Deserialize, Serializer, Deserializer};
    use serde_cbor::Value;

    pub fn serialize<S>(date: &DateTime<UTC>, serializer: S) -> Result<S::Ok, S::Error>
        where S: Serializer
    {
        serializer.serialize_str(&date.to_rfc3339())
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<UTC>, D::Error>
        where D: Deserializer<'de>
    {
        let v = Value::deserialize(deserializer)?;
        match v {
            Value::String(s) => s.parse().map_err(serde::de::Error::custom),
            _ => Ok(UTC::now()),
        }
    }
}


text:
use std::{io, str};
use std::collections::btree_map::BTreeMap;
use secstr::SecStr;
#[cfg(feature = "keepass")]
use keepass::{Database, Node, Value};
use vault::Vault;
use result::*;
use data::*;
use util;

#[derive(Debug, Default, Clone)]
pub struct ImportVault {
    entries: BTreeMap<String, (Entry, EntryMetadata)>,
}

impl Vault for ImportVault {
    fn len(&self) -> usize {
        self.entries.len()
    }

    fn entry_names<'a>(&'a self) -> Box<Iterator<Item = &'a String> + 'a> {
        Box::new(self.entries.keys())
    }

    fn get_entry(&self, name: &str) -> Result<(Entry, EntryMetadata)> {
        self.entries
            .get(name)
            .map(|x| x.to_owned())
            .ok_or(Error::EntryNotFound)
    }
}

// Wow, keepass's format is even less structured than ours (even the title is just a "Title" field)
// Also the keepass library uses rust-crypto, which means the import will be very slow in a debug build
#[cfg(feature = "keepass")]
pub fn kdbx<T: io::Read>(source: &mut T, password: &SecStr) -> Result<ImportVault> {
    let db = Database::open(source, str::from_utf8(password.unsecure())?)?;
    let mut vault = ImportVault::default();
    for node in &db.root {
        match node {
            Node::Group(_) => {},
            Node::Entry(kentry) => {
                let mut entry = Entry::default();
                for (k, v) in kentry.fields.iter().filter(|x| x.0 != "Title") {
                    let data = match *v {
                        Value::Unprotected(ref s) => SecStr::from(s.to_owned()),
                        Value::Protected(ref s) => s.to_owned(),
                    };
                    entry.fields.insert(
                        k.to_owned(),
                        Field::Stored {
                            data: data,
                            usage: util::guess_usage_stored(k),
                        },
                    );
                }
                vault.entries.insert(
                    kentry
                        .get_title()
                        .unwrap_or("??? Untitled imported entry")
                        .to_owned(),
                    (entry, EntryMetadata::default()),
                );
            },
        }
    }
    Ok(vault)
}


text:
extern crate libsodium_sys;
use libc::size_t;
use data::{StoredUsage, DerivedUsage, PasswordTemplate, Ed25519Usage};

pub fn blake2b(key: &[u8], msg: &[u8], len: usize) -> Vec<u8> {
    let mut dst = Vec::<u8>::with_capacity(len);
    unsafe {
        libsodium_sys::crypto_generichash_blake2b(dst.as_mut_ptr() as *mut u8, len, msg.as_ptr(), msg.len() as u64, key.as_ptr() as *const u8, key.len() as size_t);
        dst.set_len(len);
    }
    dst
}

pub fn guess_usage_stored(field_name: &str) -> StoredUsage {
    let fname = field_name.to_lowercase();
    if fname.contains("pass") || fname.contains("pin") || fname.contains("code") {
        StoredUsage::Password
    } else {
        StoredUsage::Text
    }
}

pub fn guess_usage_derived(field_name: &str) -> DerivedUsage {
    let fname = field_name.to_lowercase();
    if fname.contains("sign") {
        DerivedUsage::Ed25519Key(Ed25519Usage::Signify)
    } else if fname.contains("key") || fname.contains("ssh") {
        DerivedUsage::Ed25519Key(Ed25519Usage::SSH)
    } else {
        DerivedUsage::Password(PasswordTemplate::Maximum)
    }
}


text:
use std::collections::btree_map::BTreeMap;
use time::{now, Timespec};
use std::ffi::OsStr;
use serde_bytes;
#[cfg(feature = "filesystem")]
use std::io::{Cursor, Write};
#[cfg(feature = "filesystem")]
use libc::{ENOENT, EIO};
#[cfg(feature = "filesystem")]
use fuse::*;

#[cfg(feature = "filesystem")]
const TTL: Timespec = Timespec { sec: 1, nsec: 0 }; // 1 second

#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub enum AttachmentType {
    File,
    Symlink,
    Directory,
}

impl AttachmentType {
    #[cfg(feature = "filesystem")]
    pub fn to_fuse(&self) -> FileType {
        match *self {
            AttachmentType::File => FileType::RegularFile,
            AttachmentType::Symlink => FileType::Symlink,
            AttachmentType::Directory => FileType::Directory,
        }
    }
}

#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub struct Attachment {
    pub children: BTreeMap<String, u64>,
    pub parent: u64,
    pub kind: AttachmentType,
    pub mtime: u64,
    pub ctime: u64,
    pub uid: u32,
    pub gid: u32,
    pub perm: u16,
    #[serde(with = "serde_bytes")]
    pub content: Vec<u8>,
}

impl Attachment {
    pub fn new() -> Attachment {
        let ts = now().to_timespec().sec as u64;
        Attachment {
            children: BTreeMap::new(),
            parent: 1,
            kind: AttachmentType::Directory,
            mtime: ts,
            ctime: ts,
            uid: 0,
            gid: 0,
            perm: 0o777,
            content: Vec::new(),
        }
    }

    #[cfg(feature = "filesystem")]
    pub fn to_attr(&self, id: u64) -> FileAttr {
        FileAttr {
            ino: id,
            size: self.content.len() as u64,
            blocks: 1,
            atime: Timespec { sec: self.mtime as i64, nsec: 0 },
            mtime: Timespec { sec: self.mtime as i64, nsec: 0 },
            ctime: Timespec { sec: self.ctime as i64, nsec: 0 },
            crtime: Timespec { sec: self.ctime as i64, nsec: 0 },
            kind: self.kind.to_fuse(),
            perm: self.perm,
            nlink: 1,
            uid: self.uid,
            gid: self.gid,
            rdev: 0,
            flags: 0,
        }
    }
}
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub struct Attachments {
    pub nodes: BTreeMap<u64, Attachment>,
    pub root: u64,
    pub max: u64,
}

impl Attachments {
    pub fn new() -> Attachments {
        let mut nodes = BTreeMap::new();
        nodes.insert(1, Attachment::new());
        Attachments { nodes: nodes, root: 1, max: 1 }
    }

    fn create(&mut self, parent: u64, name: &OsStr, mode: u32, kind: AttachmentType, _flags: u32) -> Option<(u64, &mut Attachment)> {
        if let Some(mut n) = self.nodes.remove(&parent) {
            if n.kind != AttachmentType::Directory {
                return None;
            }
            let mut child = Attachment::new();
            child.parent = parent;
            child.kind = kind;
            child.perm = mode as u16;
            let id = self.max;
            self.max += 1;
            self.nodes.insert(id, child);
            n.children.insert(
                name.to_str().unwrap_or("__ERROR__").to_owned(),
                id,
            );
            self.nodes.insert(parent, n);
            Some((id, self.nodes.get_mut(&id).unwrap()))
        } else {
            None
        }
    }
}

#[cfg(feature = "filesystem")]
impl Filesystem for Attachments {
    fn lookup(&mut self, _req: &Request, parent: u64, name: &OsStr, reply: ReplyEntry) {
        if let Some(n) = self.nodes.get(&parent) {
            if n.kind != AttachmentType::Directory {
                reply.error(ENOENT);
                return;
            }
            if let Some((cid, c)) = n.children
                   .get(name.to_str().unwrap_or("__ERROR__"))
                   .and_then(|cid| self.nodes.get(cid).map(|c| (cid, c))) {
                reply.entry(&TTL, &c.to_attr(*cid), 0);
            } else {
                reply.error(ENOENT);
            }
        } else {
            reply.error(ENOENT);
        }
    }

    fn getattr(&mut self, _req: &Request, ino: u64, reply: ReplyAttr) {
        if let Some(n) = self.nodes.get(&ino) {
            reply.attr(&TTL, &n.to_attr(ino));
        } else {
            reply.error(ENOENT);
        }
    }

    fn setattr(&mut self, _req: &Request, ino: u64, _mode: Option<u32>, uid: Option<u32>, gid: Option<u32>, size: Option<u64>, _atime: Option<Timespec>, mtime: Option<Timespec>, _fh: Option<u64>, _crtime: Option<Timespec>, _chgtime: Option<Timespec>, _bkuptime: Option<Timespec>, flags: Option<u32>, reply: ReplyAttr) {
        if let Some(n) = self.nodes.get_mut(&ino) {
            if let Some(uid_v) = uid {
                n.uid = uid_v;
            }
            if let Some(gid_v) = gid {
                n.gid = gid_v;
            }
            if let Some(Timespec { sec: mtime_v, nsec: _ }) = mtime {
                n.mtime = mtime_v as u64;
            }
            if let Some(size_v) = size {
                n.content.resize(size_v as usize, b'\0');
            }
            reply.attr(&TTL, &n.to_attr(ino));
        } else {
            reply.error(ENOENT);
        }
    }

    fn open(&mut self, _req: &Request, ino: u64, flags: u32, reply: ReplyOpen) {
        if let Some(n) = self.nodes.get_mut(&ino) {
            reply.opened(0, 0);
        } else {
            reply.error(ENOENT);
        }
    }

    fn read(&mut self, _req: &Request, ino: u64, _fh: u64, offset: u64, _size: u32, reply: ReplyData) {
        if let Some(n) = self.nodes.get(&ino) {
            reply.data(&n.content[offset as usize..]);
        } else {
            reply.error(ENOENT);
        }
    }

    fn readdir(&mut self, _req: &Request, ino: u64, _fh: u64, offset: u64, mut reply: ReplyDirectory) {
        if let Some(n) = self.nodes.get(&ino) {
            if n.kind != AttachmentType::Directory {
                reply.error(ENOENT);
                return;
            }
            if offset == 0 {
                reply.add(ino, 0, FileType::Directory, ".");
                reply.add(n.parent, 1, FileType::Directory, "..");
                let mut idx = 2;
                for (name, id) in &n.children {
                    if let Some(c) = self.nodes.get(&id) {
                        reply.add(*id, idx, c.kind.to_fuse(), name);
                        idx += 1;
                    }
                }
            }
            reply.ok();
        } else {
            reply.error(ENOENT);
        }
    }

    fn readlink(&mut self, _req: &Request, ino: u64, reply: ReplyData) {
        if let Some(n) = self.nodes.get(&ino) {
            if n.kind != AttachmentType::Symlink {
                reply.error(ENOENT);
                return;
            }
            reply.data(&n.content[..]);
        } else {
            reply.error(ENOENT);
        }
    }

    fn fsync(&mut self, _req: &Request, _ino: u64, _fh: u64, _datasync: bool, reply: ReplyEmpty) {
        reply.ok();
    }

    fn fsyncdir(&mut self, _req: &Request, _ino: u64, _fh: u64, _datasync: bool, reply: ReplyEmpty) {
        reply.ok();
    }

    fn mknod(&mut self, _req: &Request, parent: u64, name: &OsStr, mode: u32, _rdev: u32, reply: ReplyEntry) {
        // TODO: check mode for FIFO, devices etc.
        if let Some((cid, c)) = self.create(parent, name, mode, AttachmentType::File, 0) {
            reply.entry(&TTL, &c.to_attr(cid), 0);
        } else {
            reply.error(ENOENT);
        }
    }

    fn mkdir(&mut self, _req: &Request, parent: u64, name: &OsStr, mode: u32, reply: ReplyEntry) {
        if let Some((cid, c)) = self.create(parent, name, mode, AttachmentType::Directory, 0) {
            reply.entry(&TTL, &c.to_attr(cid), 0);
        } else {
            reply.error(ENOENT);
        }
    }

    fn create(&mut self, _req: &Request, parent: u64, name: &OsStr, mode: u32, flags: u32, reply: ReplyCreate) {
        if let Some((cid, c)) = self.create(parent, name, mode, AttachmentType::File, flags) {
            reply.created(&TTL, &c.to_attr(cid), 0, 0, flags);
        } else {
            reply.error(ENOENT);
        }
    }

    fn write(&mut self, _req: &Request, ino: u64, _fh: u64, offset: u64, data: &[u8], flags: u32, reply: ReplyWrite) {
        if let Some(mut n) = self.nodes.remove(&ino) {
            let mut cursor = Cursor::new(n.content);
            cursor.set_position(offset);
            if let Ok(()) = cursor.write_all(data) {
                reply.written(data.len() as u32);
             } else {
                reply.error(EIO);
             }
            n.content = cursor.into_inner();
            self.nodes.insert(ino, n);
        } else {
            reply.error(ENOENT);
        }
    }

    fn access(&mut self, _req: &Request, _ino: u64, _mask: u32, reply: ReplyEmpty) {
        // TODO
        reply.ok();
    }

    fn flush(&mut self, _req: &Request, _ino: u64, _fh: u64, _lock_owner: u64, reply: ReplyEmpty) {
        reply.ok();
    }
}


text:
extern crate libc;

#[macro_use]
extern crate serde_derive;
extern crate serde_bytes;
extern crate serde_cbor;
extern crate serde;
extern crate base64;
extern crate byteorder;
#[cfg(all(unix, not(target_os = "android"), not(target_os = "ios")))]
extern crate unix_socket;

extern crate secstr;
extern crate rusterpassword;
extern crate sodiumoxide;
extern crate crypto;
extern crate rand;

extern crate chrono;
#[cfg(feature = "keepass")]
extern crate keepass;

extern crate time;
#[cfg(feature = "filesystem")]
extern crate fuse;

pub mod util;
pub mod result;
pub mod data;
pub mod attachments;
pub mod vault;
pub mod encvault;
pub mod output;
pub mod merge;
pub mod import;

pub fn init() {
    sodiumoxide::init();
}


text:
#!/bin/sh

: ${NDK_STANDALONE:="/opt/ndk"}
: ${NDK_STANDALONE_ARM64:="/opt/ndk64"}
: ${NDK_STANDALONE_X86:="/opt/ndkx86"}
: ${BUILD_MODE:="debug"}
: ${RUST_PREFIX:="/opt/rust-arm"}
: ${SODIUM_ROOT:="../libsodium"}

export PATH="$RUST_PREFIX/bin:$PATH"
export DYLD_LIBRARY_PATH="$RUST_PREFIX/lib:$DYLD_LIBRARY_PATH:$LD_LIBRARY_PATH"
export PKG_CONFIG_ALLOW_CROSS=1

: ${CARGO:="`which cargo`"}

CARGO_OPTS="-v -j 4"
if [ "$BUILD_MODE" = "release" ]; then
	CARGO_OPTS="$CARGO_OPTS --$BUILD_MODE"
fi
RUSTC_OPTS="--crate-type=dylib"

echo "Android NDK standalone: $NDK_STANDALONE"
echo "Rust: $RUST_PREFIX"
echo "Cargo: $CARGO"
echo "libsodium: $SODIUM_ROOT"

set -e

echo "=> Building for arm"
DYLD_LIBRARY_PATH="$NDK_STANDALONE/lib:$DYLD_LIBRARY_PATH" \
LD_LIBRARY_PATH="$NDK_STANDALONE/lib:$LD_LIBRARY_PATH" \
PATH="$NDK_STANDALONE/arm-linux-androideabi/bin:$NDK_STANDALONE/bin:$PATH" \
SODIUM_LIB_DIR="$SODIUM_ROOT/libsodium-android-armv6/lib" \
	$CARGO rustc $CARGO_OPTS --target=arm-linux-androideabi -- $RUSTC_OPTS -C linker=arm-linux-androideabi-gcc -C ar=arm-linux-androideabi-ar

echo "=> Building for aarch64"
DYLD_LIBRARY_PATH="$NDK_STANDALONE_ARM64/lib:$DYLD_LIBRARY_PATH" \
LD_LIBRARY_PATH="$NDK_STANDALONE_ARM64/lib:$LD_LIBRARY_PATH" \
PATH="$NDK_STANDALONE_ARM64/aarch64-linux-android/bin:$NDK_STANDALONE_ARM64/bin:$PATH" \
SODIUM_LIB_DIR="$SODIUM_ROOT/libsodium-android-armv8-a/lib" \
	$CARGO rustc $CARGO_OPTS --target=aarch64-linux-android -- $RUSTC_OPTS -C linker=aarch64-linux-android-gcc -C ar=aarch64-linux-android-ar

echo "=> Building for i686"
DYLD_LIBRARY_PATH="$NDK_STANDALONE_X86/lib:$DYLD_LIBRARY_PATH" \
LD_LIBRARY_PATH="$NDK_STANDALONE_X86/lib:$LD_LIBRARY_PATH" \
PATH="$NDK_STANDALONE_X86/i686-linux-android/bin:$NDK_STANDALONE_X86/bin:$PATH" \
SODIUM_LIB_DIR="$SODIUM_ROOT/libsodium-android-i686/lib" \
	$CARGO rustc $CARGO_OPTS --target=i686-linux-android -- $RUSTC_OPTS -C linker=i686-linux-android-gcc -C ar=i686-linux-android-ar

echo "=> Moving files"
mkdir -p "target/android-all/$BUILD_MODE/x86"
cp "target/i686-linux-android/$BUILD_MODE/libfreepass_capi.so" "target/android-all/$BUILD_MODE/x86"
mkdir -p "target/android-all/$BUILD_MODE/armeabi"
cp "target/arm-linux-androideabi/$BUILD_MODE/libfreepass_capi.so" "target/android-all/$BUILD_MODE/armeabi"
mkdir -p "target/android-all/$BUILD_MODE/armeabi-v7a"
cp "target/arm-linux-androideabi/$BUILD_MODE/libfreepass_capi.so" "target/android-all/$BUILD_MODE/armeabi-v7a"
mkdir -p "target/android-all/$BUILD_MODE/arm64-v8a"
cp "target/aarch64-linux-android/$BUILD_MODE/libfreepass_capi.so" "target/android-all/$BUILD_MODE/arm64-v8a"


text:
#ifdef __cplusplus
extern "C" {
#endif

#include <stdint.h>
#include "../rusterpassword/capi/rusterpassword_capi.h"

typedef struct vault_S vault_t;
typedef struct string_iter_S string_iter_t;
typedef struct {
	uint8_t *data;
	size_t len;
	size_t cap;
} vector_t;

void freepass_init();

secstr_t* freepass_gen_outer_key(const secstr_t*);
secstr_t* freepass_gen_entries_key(const secstr_t*);
void freepass_free_outer_key(const secstr_t*);
void freepass_free_entries_key(const secstr_t*);

vault_t* freepass_open_vault(const char*, const secstr_t*, const secstr_t*);
vault_t* freepass_new_vault(const secstr_t*, const secstr_t*);
void freepass_close_vault(const vault_t*);

string_iter_t* freepass_vault_get_entry_names_iterator(const vault_t*);
char* freepass_entry_names_iterator_next(string_iter_t*);
void freepass_free_entry_name(char*);
void freepass_free_entry_names_iterator(string_iter_t*);

vector_t freepass_vault_get_entry_cbor(const vault_t*, const char*);
void freepass_free_entry_cbor(vector_t);
void freepass_vault_put_entry_cbor(const vault_t*, const char*, uint8_t*, size_t);

#ifdef __cplusplus
}
#endif


text:
[package]
name = "freepass-capi"
version = "0.0.0"
authors = [ "Greg V <greg@unrelenting.technology>" ]
keywords = [ "Security", "Password", "MasterPassword" ]
description = "Freepass password/auth manager: C API"
license = "Unlicense"
readme = "README.md"
homepage = "https://github.com/myfreeweb/freepass"
repository = "https://github.com/myfreeweb/freepass"

[lib]
name = "freepass_capi"
crate-type = [ "dylib", "staticlib" ]

[dependencies]
freepass-core = { version = "0", path = "../core", default-features = false, features = [] }
secstr = { version = "0.3", features = ["serde", "libsodium-sys"] }
serde_cbor = "0.6"
sodiumoxide = "0"
rusterpassword = { version = "0", path = "../rusterpassword" }
rusterpassword-capi = { version = "0", path = "../rusterpassword/capi" }
colorhash256 = "0"
libc = "0.2"


text:
#!/bin/sh

: ${CARGO:="`which cargo`"}
: ${LIPO:="`which lipo`"}
: ${BUILD_MODE:="release"}
: ${RUST_PREFIX:="/opt/rust-arm"}
: ${SODIUM_ROOT:="../libsodium"}

export PATH="$RUST_PREFIX/bin:$PATH"
export DYLD_LIBRARY_PATH="$RUST_PREFIX/lib:$DYLD_LIBRARY_PATH"
# export PKG_CONFIG_ALLOW_CROSS=1
export SODIUM_LIB_DIR="$SODIUM_ROOT/libsodium-ios/lib" \

LIBNAME="libfreepass_capi.a"
CARGO_OPTS="-v"
if [[ "$BUILD_MODE" == "release" ]]; then
	CARGO_OPTS="$CARGO_OPTS --$BUILD_MODE"
fi
RUSTC_OPTS="--crate-type=staticlib"

set -e

echo "=> Building for armv7"
$CARGO rustc $CARGO_OPTS --target=armv7-apple-ios -- $RUSTC_OPTS

echo "=> Building for aarch64"
$CARGO rustc $CARGO_OPTS --target=aarch64-apple-ios -- $RUSTC_OPTS

## Uncomment i386 for 32-bit Simulator (or profiling o_0)
echo "=> Building for i386"
$CARGO rustc $CARGO_OPTS --target=i386-apple-ios -- $RUSTC_OPTS

echo "=> Building for x86_64"
$CARGO rustc $CARGO_OPTS --target=x86_64-apple-ios -- $RUSTC_OPTS

echo "=> Making universal library"
mkdir -p "target/universal/$BUILD_MODE"
$LIPO -create -output "target/universal/$BUILD_MODE/$LIBNAME" \
	"target/armv7-apple-ios/$BUILD_MODE/$LIBNAME" \
	"target/aarch64-apple-ios/$BUILD_MODE/$LIBNAME" \
	"target/x86_64-apple-ios/$BUILD_MODE/$LIBNAME" \
	"target/i386-apple-ios/$BUILD_MODE/$LIBNAME" \


text:
# freepass-capi [![unlicense](https://img.shields.io/badge/un-license-green.svg?style=flat)](http://unlicense.org)

The free password manager for power users: C API for the core library.

## Building (for Android and iOS)

Get the Android NDK and [build a standalone toolchain](https://developer.android.com/ndk/guides/standalone_toolchain.html):

```bash
$ sudo chown $(whoami) /opt
$ brew install android-ndk
$ cd $(brew --prefix android-ndk)
$ build/tools/make_standalone_toolchain.py --stl libc++ --api 24 --force --arch arm   --install-dir /opt/ndk
$ build/tools/make_standalone_toolchain.py --stl libc++ --api 24 --force --arch arm64 --install-dir /opt/ndk64
$ build/tools/make_standalone_toolchain.py --stl libc++ --api 24 --force --arch x86   --install-dir /opt/ndkx86
```

(On FreeBSD, edit `make_standalone_toolchain.py`: `if platform.system() == 'Linux'` -> `if platform.system() == 'Linux' or platform.system() == 'FreeBSD'`)

Add the Rust targets:

```bash
$ rustup target add arm-linux-androideabi aarch64-linux-android i686-linux-android
```

Get submodules of this repo, fix the libsodium build scripts if building for iOS, build it:

```bash
$ git submodule update --init
$ cd libsodium
$ ./autogen.sh

$ vi dist-build/ios.sh # Remove "--enable-minimal"
$ ./dist-build/ios.sh

$ ../build-libsodium-android.sh
```

Finally, build this library:

```bash
$ export RUST_PREFIX=/opt/rust-arm # ... customize some variables if necessary (see the scripts)
# (if using `rustup`, something like `export RUST_PREFIX=~/.multirust/toolchains/stable-x86_64-apple-darwin`)
$ ./build-android.sh
$ ./build-ios.sh
```

If you have `dyld` errors, it's probably because of El Capitan's security features.
Make sure your `cargo` is up to date.
If you use multirust on El Capitan, try something like this:

```bash
$ CARGO=$(multirust which cargo) ./build-android.sh
$ CARGO=$(multirust which cargo) ./build-ios.sh
```

## Project-related stuff

See `../README.md`.


text:
#![allow(no_mangle_generic_items)]

extern crate libc;
extern crate serde_cbor;
extern crate secstr;
extern crate rusterpassword_capi;
extern crate freepass_core;

pub use rusterpassword_capi::*;

use std::{ptr, fs, mem, slice};
use std::ffi::*;
use libc::*;
use secstr::*;
use freepass_core::data::*;
use freepass_core::vault::*;
use freepass_core::encvault::*;
// use freepass_core::output::*;

#[repr(C)]
pub struct CVector {
    data: *mut u8,
    len: size_t,
    cap: size_t,
}

macro_rules! from_c {
    ( obj $o:expr ) => { unsafe { assert!(!$o.is_null()); &*$o } };
    ( mut obj $o:expr ) => { unsafe { assert!(!$o.is_null()); &mut *$o } };
    ( cstr $s:expr ) => { unsafe { assert!(!$s.is_null()); CStr::from_ptr($s) }.to_str().unwrap() };
    ( slice $s:expr, $len:expr ) => { unsafe { assert!(!$s.is_null()); slice::from_raw_parts($s, $len as usize) } };
}

macro_rules! to_c {
    ( obj $o:expr ) => { Box::into_raw(Box::new($o)) };
    ( cstr $s:expr ) => { CString::new($s).unwrap().into_raw() };
    ( vec $s:expr ) => { {
        let mut v = $s;
        v.shrink_to_fit();
        let result = CVector { data: v.as_mut_ptr(), len: v.len(), cap: v.capacity() };
        mem::forget(v);
        result
    } }
}

#[no_mangle]
pub extern "C" fn freepass_init() {
    freepass_core::init();
}

#[no_mangle]
pub extern "C" fn freepass_gen_outer_key(master_key_c: *const SecStr) -> *mut SecStr {
    to_c![obj gen_outer_key(from_c![obj master_key_c])]
}

#[no_mangle]
pub unsafe extern "C" fn freepass_free_outer_key(outer_key_c: *mut SecStr) {
    Box::from_raw(outer_key_c);
}

#[no_mangle]
pub extern "C" fn freepass_gen_entries_key(master_key_c: *const SecStr) -> *mut SecStr {
    to_c![obj gen_entries_key(from_c![obj master_key_c])]
}

#[no_mangle]
pub unsafe extern "C" fn freepass_free_entries_key(entries_key_c: *mut SecStr) {
    Box::from_raw(entries_key_c);
}

#[no_mangle]
pub extern "C" fn freepass_open_vault(file_path_c: *const c_char, entries_key_c: *const SecStr, outer_key_c: *const SecStr) -> *mut DecryptedVault {
    let file_path = from_c![cstr file_path_c];
    let entries_key = from_c![obj entries_key_c];
    let outer_key = from_c![obj outer_key_c];
    if let Ok(file) = fs::OpenOptions::new().read(true).write(true).open(
        &file_path,
    ) {
        match DecryptedVault::open(entries_key.clone(), outer_key.clone(), file) {
            Ok(vault) => return to_c![obj vault],
            Err(e) => println!("Couldn't open DecryptedVault: {:?}", e),
        }
    } else {
        println!("Couldn't open file");
    }
    return ptr::null_mut();
}

#[no_mangle]
pub extern "C" fn freepass_new_vault(entries_key_c: *const SecStr, outer_key_c: *const SecStr) -> *mut DecryptedVault {
    to_c![obj DecryptedVault::new(from_c![obj entries_key_c].clone(), from_c![obj outer_key_c].clone())]
}

#[no_mangle]
pub unsafe extern "C" fn freepass_close_vault(vault_c: *mut DecryptedVault) {
    Box::from_raw(vault_c);
}

#[no_mangle]
pub extern "C" fn freepass_vault_get_entry_names_iterator<'a>(vault_c: *const DecryptedVault) -> *mut Box<Iterator<Item = &'a String> + 'a> {
    to_c![obj from_c![obj vault_c].entry_names()]
}

#[no_mangle]
pub unsafe extern "C" fn freepass_entry_names_iterator_next<'a>(iter_c: *mut Box<Iterator<Item = &'a String> + 'a>) -> *mut c_char {
    match from_c![mut obj iter_c].next() {
        Some(s) => to_c![cstr s.clone()],
        None => ptr::null_mut(),
    }
}

#[no_mangle]
pub unsafe extern "C" fn freepass_free_entry_name(name_c: *mut c_char) {
    CString::from_raw(name_c);
}

#[no_mangle]
pub unsafe extern "C" fn freepass_free_entry_names_iterator<'a>(iter_c: *mut Box<Iterator<Item = &'a String> + 'a>) {
    Box::from_raw(iter_c);
}

#[no_mangle]
pub extern "C" fn freepass_vault_get_entry_cbor(vault_c: *const DecryptedVault, name_c: *const c_char) -> CVector {
    if let Ok((mut entry_cbor, metadata)) = from_c![obj vault_c].get_entry_cbor(from_c![cstr name_c]) {
        // Optimization: avoid decoding entry just to re-encode it
        entry_cbor.insert(0, 0x82); // Array of length 2
        serde_cbor::ser::to_writer(&mut entry_cbor, &[metadata]).unwrap();
        to_c![vec entry_cbor]
    } else {
        let empty = Vec::new();
        to_c![vec empty]
    }
}

#[no_mangle]
pub unsafe extern "C" fn freepass_free_entry_cbor(cbor_c: CVector) {
    Vec::<u8>::from_raw_parts(cbor_c.data, cbor_c.len, cbor_c.cap);
}

#[no_mangle]
pub extern "C" fn freepass_vault_put_entry_cbor(vault_c: *mut DecryptedVault, name_c: *const c_char, cbor_c: *const u8, cbor_len_c: size_t) {
    let (entry, mut metadata) = serde_cbor::from_slice::<Vec<(Entry, EntryMetadata)>>(from_c![slice cbor_c, cbor_len_c])
        .unwrap()
        .pop()
        .unwrap();
    from_c![mut obj vault_c]
        .put_entry(from_c![cstr name_c], &entry, &mut metadata)
        .unwrap()
}


text: paths = []


text:
import UIKit

class EntryCell: UITableViewCell {
	@IBOutlet weak var entryName: UILabel!
}

let NEW_ENTRY_NAME = "New entry"

class VaultViewController: UITableViewController, UISearchResultsUpdating {

	var entryViewController: EntryViewController? = nil
	var entrySearchController = UISearchController(searchResultsController: nil)
	var entryNames = [String]()
	var filteredEntryNames = [String]()

	override func viewDidLoad() {
		super.viewDidLoad()
		entryNames = Vault.entryNames()

		self.definesPresentationContext = true
		let addButton = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: "insertNewObject:")
		self.navigationItem.rightBarButtonItem = addButton
		if let split = self.splitViewController {
			let controllers = split.viewControllers
			self.entryViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? EntryViewController
		}
		self.entrySearchController.searchResultsUpdater = self
		self.entrySearchController.dimsBackgroundDuringPresentation = false
		self.entrySearchController.hidesNavigationBarDuringPresentation = false
		self.entrySearchController.searchBar.sizeToFit()
		self.entrySearchController.searchBar.searchBarStyle = .Minimal
		self.tableView.tableHeaderView = self.entrySearchController.searchBar
		self.tableView.reloadData()
	}

	override func viewWillAppear(animated: Bool) {
		self.clearsSelectionOnViewWillAppear = self.splitViewController!.collapsed
		super.viewWillAppear(animated)
	}

	override func didReceiveMemoryWarning() {
		super.didReceiveMemoryWarning()
	}

	func insertNewObject(sender: AnyObject) {
		entryNames.insert(NEW_ENTRY_NAME, atIndex: 0)
		let indexPath = NSIndexPath(forRow: 0, inSection: 0)
		self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
	}

	// MARK: - Segues

	override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
		if segue.identifier == "showEntry" {
			if let indexPath = self.tableView.indexPathForSelectedRow {
				let entryName = entryNames[indexPath.row]
				let controller = (segue.destinationViewController as! UINavigationController).topViewController as! EntryViewController
				controller.entryName.value = entryName
				if (entryName != NEW_ENTRY_NAME) {
					controller.entry = Vault.getEntry(entryName)
				} else {
					controller.entry = Entry()
				}
				controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem()
				controller.navigationItem.leftItemsSupplementBackButton = true
			}
		}
	}

	// MARK: - Table View

	override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
		return 1
	}

	override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
		if (self.entrySearchController.active) {
			return self.filteredEntryNames.count
		} else {
			return self.entryNames.count
		}
	}

	override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
		let cell = tableView.dequeueReusableCellWithIdentifier("EntryCell", forIndexPath: indexPath) as! EntryCell
		let entryName: String
		if (self.entrySearchController.active) {
			entryName = filteredEntryNames[indexPath.row]
		} else {
			entryName = entryNames[indexPath.row]
		}
		cell.entryName.text = entryName
		cell.entryName.textColor = Colors.primaryContent
		return cell
	}

	func updateSearchResultsForSearchController(searchController: UISearchController) {
		self.filteredEntryNames.removeAll(keepCapacity: false)
		let array = (self.entryNames as NSArray).filteredArrayUsingPredicate(
			NSPredicate(format: "SELF CONTAINS[c] %@", self.entrySearchController.searchBar.text!)
		)
		self.filteredEntryNames = array as! [String]
		self.tableView.reloadData()
	}

}


text:
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

	var window: UIWindow?

	func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
		// Override point for customization after application launch.
		freepass_init()
		Colors.setup()
		return true
	}

	func applicationWillResignActive(application: UIApplication) {
		// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
		// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
	}

	func applicationDidEnterBackground(application: UIApplication) {
		// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
		// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
	}

	func applicationWillEnterForeground(application: UIApplication) {
		// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
	}

	func applicationDidBecomeActive(application: UIApplication) {
		// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
	}

	func applicationWillTerminate(application: UIApplication) {
		Vault.close()
	}

}


text:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>en</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>$(PRODUCT_NAME)</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0</string>
	<key>CFBundleSignature</key>
	<string>????</string>
	<key>CFBundleVersion</key>
	<string>1</string>
	<key>LSRequiresIPhoneOS</key>
	<true/>
	<key>UIFileSharingEnabled</key>
	<true/>
	<key>UILaunchStoryboardName</key>
	<string>LaunchScreen</string>
	<key>UIMainStoryboardFile</key>
	<string>Main</string>
	<key>UIRequiredDeviceCapabilities</key>
	<array>
		<string>armv7</string>
	</array>
	<key>UIStatusBarStyle</key>
	<string>UIStatusBarStyleLightContent</string>
	<key>UIStatusBarTintParameters</key>
	<dict>
		<key>UINavigationBar</key>
		<dict>
			<key>Style</key>
			<string>UIBarStyleDefault</string>
			<key>Translucent</key>
			<false/>
		</dict>
	</dict>
	<key>UISupportedInterfaceOrientations</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
	<key>UISupportedInterfaceOrientations~ipad</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationPortraitUpsideDown</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
	<key>UIViewControllerBasedStatusBarAppearance</key>
	<false/>
</dict>
</plist>


text:
//
//  Use this file to import your target's public headers that you would like to expose to Swift.
//

// Build Settings -> Search Paths -> Header Search Paths

#include <freepass_capi.h>


text:
import UIKit
import RxSwift

class EntryViewController: UITableViewController {

	@IBOutlet weak var editButton: UIBarButtonItem!

	let dbag = DisposeBag()
	let inEditMode = Variable(false)
	var fieldModels : [FieldViewModel] = []
	let entryName = Variable("")

	var entry: Entry? {
		didSet {
//			print(entry!.fields)
			setFieldModels(entry!.fields)
		}
	}

	func setFieldModels(fields: [(String, Field)]) {
		self.fieldModels = fields.map { (k, v) in FieldViewModel(name: k, field: v) }
		self.tableView.reloadData()
	}

	@IBAction func toggleEdit(sender: AnyObject) {
		self.inEditMode.value = !self.inEditMode.value
		if (!self.inEditMode.value) {
			entry!.fields = 	self.fieldModels.flatMap { $0.toField() }
//			print(entry!.fields)
			// TODO: save
		}
	}

	func cancelEdit(sender: AnyObject) {
		self.inEditMode.value = false
		setFieldModels(self.entry!.fields)
	}

	override func viewDidLoad() {
		super.viewDidLoad()
		self.tableView.backgroundColor = Colors.primaryBackground
		self.tableView.rowHeight = UITableViewAutomaticDimension
		self.tableView.allowsSelectionDuringEditing = false
		let cancelButton = UIBarButtonItem(title: "Cancel", style: .Plain, target: self, action: "cancelEdit:")
		self.entryName.asObservable().subscribeNext { self.title = $0 }.addDisposableTo(dbag)
		self.inEditMode.asObservable().distinctUntilChanged().subscribeNext {
			self.navigationItem.setHidesBackButton($0, animated: true)
			self.navigationController?.interactivePopGestureRecognizer?.enabled = !$0
			self.navigationItem.setLeftBarButtonItem($0 ? cancelButton : nil, animated: true)
			self.editButton.title = $0 ? "Save" : "Edit"
			self.tableView.editing = $0
			self.tableView.estimatedRowHeight = $0 ? 140.0 : 80.0
			self.tableView.reloadData()
		}.addDisposableTo(dbag)
		self.tableView.reloadData()
	}

	override func didReceiveMemoryWarning() {
		super.didReceiveMemoryWarning()
	}

	// MARK: - Table View

	override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
		return 1
	}

	override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
		return self.fieldModels.count
	}

	override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
		let field = fieldModels[indexPath.row]
		if (inEditMode.value) {
			let cell = EditFieldCell.init(style: .Default, reuseIdentifier: nil)
			cell.setField(field)
			return cell
		} else {
			let cell = ShowPasswordFieldCell.init(style: .Default, reuseIdentifier: nil)
			cell.setField(field)
			return cell
		}
	}

}


text:
import Foundation
import SwiftCBOR

enum VaultError : ErrorType {
	case WrongPassword
	case UnknownError
}

struct Vault {
	private static var masterKey : COpaquePointer? = nil
	private static var vaultObj : COpaquePointer? = nil
	private static var entriesKey : COpaquePointer? = nil
	private static var filePath : String? = nil
	private static var isOpen = false

	static func open(path: String, userName: String, password: String) throws {
		masterKey = rusterpassword_gen_master_key(password, userName)
		if masterKey == COpaquePointer.init(nilLiteral: ()) {
			throw VaultError.UnknownError
		}
		let outerKey = freepass_gen_outer_key(masterKey!)
		entriesKey = freepass_gen_entries_key(masterKey!)

		let fm = NSFileManager.defaultManager()
		if fm.isReadableFileAtPath(path) {
			filePath = path
			vaultObj = freepass_open_vault(path, entriesKey!, outerKey)
		} else {
			vaultObj = freepass_new_vault(entriesKey!, outerKey)
		}
		freepass_free_outer_key(outerKey)

		if vaultObj == COpaquePointer.init(nilLiteral: ()) {
			rusterpassword_free_master_key(masterKey!)
			freepass_free_entries_key(entriesKey!)
			throw VaultError.WrongPassword
		}
		isOpen = true
	}

	static func entryNames() -> [String] {
		guard let vaultObj = vaultObj else { return [] }
		let names_iter = freepass_vault_get_entry_names_iterator(vaultObj)
		defer { freepass_free_entry_names_iterator(names_iter) }
		var names = [String]()
		var curr = freepass_entry_names_iterator_next(names_iter)
		while curr != UnsafeMutablePointer.init(nilLiteral: ()) {
			names.append(String.fromCString(curr)!)
			freepass_free_entry_name(curr)
			curr = freepass_entry_names_iterator_next(names_iter)
		}
		return names
	}

	static func getEntry(name: String) -> Entry? {
		guard let vaultObj = vaultObj else { return nil }
		guard let entriesKey = entriesKey else { return nil }
		let cbor = freepass_vault_get_entry_cbor(vaultObj, name)
		defer { freepass_free_entry_cbor(cbor) }
		let bytes = Array(UnsafeBufferPointer(start: cbor.data, count: cbor.len))
		guard let result = try! CBORDecoder(input: bytes).decodeItem() else { return nil }
		return Entry(fromCbor: result)
	}

	static func close() {
		if let masterKey = masterKey {
			rusterpassword_free_master_key(masterKey)
		}
		masterKey = nil

		if let entriesKey = entriesKey {
			freepass_free_entries_key(entriesKey)
		}

		masterKey = nil
		if let vaultObj = vaultObj {
			freepass_close_vault(vaultObj)
		}
		isOpen = false
	}
}


text:
import SwiftCBOR

class Entry {
	var fields: [(String, Field)] = []

	init?(fromCbor arr: CBOR) {
		guard case let CBOR.Map(field_objs)? = arr[0]?["fields"] else { return nil }
		field_objs.forEach {
			guard case let CBOR.UTF8String(key) = $0 else { return }
			guard let value = Field(fromCbor: $1) else { return }
			fields.append((key, value))
		}
	}

	init() {}
}


text:
import Foundation
import RxSwift
import RxCocoa

// https://github.com/ReactiveX/RxSwift/blob/master/RxExample/RxExample/Operators.swift#L20
// Two way binding operator between control property and variable, that's all it takes

infix operator <-> {}

func <-> <T>(property: ControlProperty<T>, variable: Variable<T>) -> Disposable {
	let bindToUIDisposable = variable.asObservable().bindTo(property)
	let bindToVariable = property
		.subscribe(onNext: { n in
			variable.value = n
			}, onCompleted: {
				bindToUIDisposable.dispose()
			})
	return StableCompositeDisposable.create(bindToUIDisposable, bindToVariable)
}

func transformBind<T1, T2>(property: ControlProperty<T1>, variable: Variable<T2>, propToVar: T1 -> T2, varToProp: T2 -> T1) -> Disposable {
	let bindToUIDisposable = variable.asObservable().map(varToProp).bindTo(property)
	let bindToVariable = property
		.subscribe(onNext: { n in
			variable.value = propToVar(n)
			}, onCompleted: {
				bindToUIDisposable.dispose()
			})
	return StableCompositeDisposable.create(bindToUIDisposable, bindToVariable)
}


text:
import UIKit
import HexColor

// http://flatuicolors.com

struct Colors {
	static let wisteria = UIColor(0x8e44ad)
	static let clouds = UIColor(0xecf0f1)
	static let silver = UIColor(0xbdc3c7)
	static let wetAsphalt = UIColor(0x34495e)

	static let primaryBackground = silver
	static let primaryContent = wetAsphalt
	static let primaryAccent = wisteria

	static let secondaryBackground = clouds

	static let toolbarBackground = wisteria
	static let toolbarContent = clouds

	static func setup() {
		UILabel.appearance().tintColor = primaryContent
		UITextView.appearance().tintColor = primaryContent
		UITableView.appearance().sectionIndexBackgroundColor = primaryBackground
		UITableView.appearance().backgroundColor = secondaryBackground
		UITableViewCell.appearance().backgroundColor = secondaryBackground
		UIButton.appearance().tintColor = primaryAccent
		UISearchBar.appearance().tintColor = primaryAccent

		UINavigationBar.appearance().translucent = false
		UINavigationBar.appearance().barTintColor = toolbarBackground
		UINavigationBar.appearance().tintColor = toolbarContent
		UINavigationBar.appearance().titleTextAttributes = [NSForegroundColorAttributeName: toolbarContent]
		UIBarButtonItem.appearance().tintColor = toolbarContent
	}
}


text:
import UIKit

// Have to use this instead of a weak ref because for some weird FUCKING reason,
// a weak ref somehow prevents the first cell from being deleted when you stop editing
// https://stackoverflow.com/a/26469012
extension UITableViewCell {
	var tableView: UITableView? {
		get {
			for var view = self.superview; view != nil; view = view!.superview {
				if view! is UITableView {
					return (view! as! UITableView)
				}
			}
			return nil
		}
	}
}


text:
import SwiftCBOR

enum DerivedUsage {
	case Password(template: String)
	case Ed25519Key(usage: String)
	case RawKey

	init?(fromCbor obj: CBOR) {
		switch obj["variant"] {
		case "Password"?:
			guard case let CBOR.UTF8String(template)? = obj["fields"]?[0] else { return nil }
			self = .Password(template: template)
		case "Ed25519Key"?:
			guard case let CBOR.UTF8String(usage)? = obj["fields"]?[0] else { return nil }
			self = .Ed25519Key(usage: usage)
		case "RawKey"?:
			self = .RawKey
		default: return nil
		}
	}
}

enum StoredUsage {
	case Text
	case Password

	init?(fromCbor obj: CBOR) {
		switch obj {
		case "Text": self = .Text
		case "Password": self = .Password
		default: return nil
		}
	}
}

enum Field {
	case Derived(counter: UInt32, site_name: String?, usage: DerivedUsage)
	case Stored(data: [UInt8], usage: StoredUsage)

	init?(fromCbor obj: CBOR) {
		switch obj["variant"] {
		case "Derived"?:
			guard case let CBOR.UnsignedInt(counter)? = obj["fields"]?[0] else { return nil }
			let site_name: String?
			if case CBOR.UTF8String(let site_name_)? = obj["fields"]?[1] { site_name = site_name_ } else { site_name = nil }
			guard let uobj = obj["fields"]?[2], usage = DerivedUsage(fromCbor: uobj) else { return nil }
			self = .Derived(counter: UInt32(counter), site_name: site_name, usage: usage)
		case "Stored"?:
			guard case let CBOR.ByteString(data)? = obj["fields"]?[0] else { return nil }
			guard let uobj = obj["fields"]?[1], usage = StoredUsage(fromCbor: uobj) else { return nil }
			self = .Stored(data: data, usage: usage)
		default: return nil
		}
	}

	init() {
		self = .Derived(counter: 1, site_name: nil, usage: .Password(template: "Maximum"))
	}
}


text:
import UIKit

class LoginViewController: UITableViewController, UISplitViewControllerDelegate {

	@IBOutlet weak var userName: UITextField!
	@IBOutlet weak var password: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()
		self.view.backgroundColor = Colors.primaryAccent
		print(documentsPath()?.path)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

	func documentsPath() -> NSURL? {
		return NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first
	}

    // MARK: - Navigation

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
		if segue.identifier == "openVault" {
			let splitViewController = segue.destinationViewController as! UISplitViewController
			let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController
			navigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem()
			splitViewController.delegate = self
		}
    }

	override func shouldPerformSegueWithIdentifier(identifier: String, sender: AnyObject?) -> Bool {
		if identifier == "openVault" {
			do {
				let path = documentsPath()?.URLByAppendingPathComponent("test.fpass").path! // TODO file selection
				try Vault.open(path!, userName: userName.text!, password: password.text!)
			} catch {
				let alert = UIAlertController(title: "Error", message: "Error", preferredStyle: UIAlertControllerStyle.Alert)
				alert.addAction(UIAlertAction(title: "OK", style: .Default) { (action) in })
				self.presentViewController(alert, animated: true, completion: nil)
				return false
			}
		}
		return true
	}

	// MARK: - Split view

	func splitViewController(splitViewController: UISplitViewController, collapseSecondaryViewController secondaryViewController:UIViewController, ontoPrimaryViewController primaryViewController:UIViewController) -> Bool {
		guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false }
		guard let topAsDetailController = secondaryAsNavController.topViewController as? EntryViewController else { return false }
		if topAsDetailController.entry == nil {
			// Return true to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
			return true
		}
		return false
	}

}


text:
import Foundation
import RxSwift

class FieldViewModel {
	enum FieldType {
		case Stored
		case Derived
	}

	let dbag = DisposeBag()
	let field_type: Variable<FieldType?> = Variable(nil)
	let field_name = Variable("")
	let derived_counter: Variable<UInt32> = Variable(0)
	let derived_site_name = Variable("")
	let derived_usage: Variable<DerivedUsage?> = Variable(nil)
	let stored_data: Variable<[UInt8]?> = Variable(nil)
	let stored_data_string = Variable("")
	let stored_usage: Variable<StoredUsage?> = Variable(nil)

	func init_stored_data_conversion() {
		var updatingFromSelf = false
		stored_data.asObservable().subscribeNext {
			if updatingFromSelf { return }
			let b = $0 ?? []
			if let s = NSString(bytes: b, length: b.count, encoding: NSUTF8StringEncoding) as? String {
				self.stored_data_string.value = s
			}
		}.addDisposableTo(dbag)
		stored_data_string.asObservable().subscribeNext {
			updatingFromSelf = true
			self.stored_data.value = $0.utf8.map { $0 }
			updatingFromSelf = false
		}.addDisposableTo(dbag)
	}

	init(name: String, field: Field) {
		init_stored_data_conversion()
		field_name.value = name
		switch field {
		case .Derived(let counter, let site_name, let usage):
			field_type.value = .Derived
			derived_counter.value = counter
			derived_site_name.value = site_name ?? ""
			derived_usage.value = usage
		case .Stored(data: let data, usage: let usage):
			field_type.value = .Stored
			stored_data.value = data
			stored_usage.value = usage
		}
	}

	func toField() -> (String, Field)? {
		switch (field_name.value, field_type.value) {
		case (let name, .Derived?):
			return (name, .Derived(counter: derived_counter.value ?? 1, site_name: derived_site_name.value, usage: derived_usage.value ?? .Password(template: "Maximum")))
		case (let name, .Stored?):
			return (name, .Stored(data: stored_data.value ?? [], usage: stored_usage.value ?? .Password))
		default: return nil
		}
	}
}