pelican

package module
v0.0.0-...-9fa7a59 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 31, 2015 License: MIT Imports: 35 Imported by: 0

README

pelican-protocol: Do-It-Yourself-Trust

diagram of pelican protocol in action

Status

(DO NOT USE YET!)

Still pre-alpha, lots of bugs and it is incomplete. Think of the description below as a proposal for the future.

Overview

The Pelican Protocol provides a means of devouring phishing attempts. It applies SSH technology to the web, bringing the technology developed for secure console login to everyone. It authenticates both web users and web servers. Every website you visit using the Pelican Protocol knows you by a unique key, and it is impossible to mix up credentials and sites. Instead of 100s of passwords to create and remember, there is a single strong pass phrase that is entered once at machine start time.

This is a greenfield project. We deliberately ignore the prior work of TLS, SSL, and certificate authorities.

We propose a decentalized, do-it-yourself trust model. Our design offers a sane user experience for both users of web browsers and for web application developers.

Advantages for website developers and operators

Suppose you are a website owner who doesn't currently have SSL/TLS certificates and is running a good old plain HTTP site. You think TLS/x509 certificates are painful and costly, and you are right. By running the Pelican Server (reverse proxy) on your machine, you provide a zero-cost, high-security means of accessing your plain http website. And if you decide to get certificates later, you can still keep running Pelican's additional anti-phishing protection for stronger anti-phishing.

Advantages for web browsers

Now suppose you are browsing the web using your favorite browser. The Pelican client (socks proxy) stores and manages your passwords (and SSH private keys) locally for you. The Pelican client provides you strong Man-in-the-Middle protection, and you never had to mess with certificates or pay a dime. You can still choose to browse any site anonymously, or you can authenticate strongly using your SSH private key. Backups are built-in so key recovery and moving keys to a new computer or phone is easy. Phishing is eliminated because once you've seen the real site, the Pelican client cannot be fooled by later seeing a look-alike site. This is the Trust-On-First-Use (TOFU) approach to security. TOFU is effective because most phishing attacks are short-lived and occur well after your first visit to a site. WiFi hotspot SSL stripping is a thing of the past with Pelican on your side.

And even if an attacker happens to Man-In-The-Middle your first visit to a website, you are still protected: Pelican never re-uses credentials for different servers. The worst that can happen is that you create an account on a fake phishing site. However those credentials will never ever be used to authenticate against the real website. The worst that happens is that you learn later that somebody Man-in-the-middled your previous account creation on a fake website. Evesdroppers never learn any re-usable passwords.

Summary

The Pelican Protocol provides means of doing user creation and authentication over an SSH-based protocol that tunnels http. The Pelican protocol uses the SSH protocol for key-exchange and client-to-server port forwarding protocol; however Pelican but does not allow remote execution of programs or shells. Pelican aims for strong usability by everyone, and provides for portable and easy key management. A proxy for the client side does the key management and server identity checking, acting in the role of ssh agent and client. The server provides a reverse proxy so you don't have to change your webserver's configuration. The Pelican server provides an auto-login mechanism, easy key rotation and server key backup, and can be configured to allow logins for users only from known hosts. Two factor authentication via sms/text is available. Deploy Pelican today and devour phish!

Questions

How does Pelican Client know that the Pelican Server is running or not, if both Pelican and http are travelling to server port 80? Does this leave downgrade attacks a possibility by an active MITM SSL-stripping style attack?

A: First, we can't solve all problems at once. An active MITM attack via protocol stripping is not the threat addressed here. The problem that Pelican solves is phishing via email links that take you to impersonated websites. The reverse proxy on the server can readily distinguish Pelican traffic from non-Pelican http requests, and the client can recognize if Pelican is in use our not. Once Pelican Protocol is detected for a given server, the client can remember this and insist on Pelican in the future (to detect/deflect downgrade attacks).

How does the Pelican protocol achieve backwards compatibility with existing web clients and servers?

A: For each URL requested, the Pelican Client first attempts to connect to that host using the Pelican protocol. In doing so, it determines whether the host is running HTTP or the Pelican Protocol. If the Pelican server is found, then the server is authenticated and the client's http or https traffic is tunnelled to the webserver via an SSH secured forward-only channel. If the Pelican server is not present, then a normal HTTP connection is used. This means that a user can run the Pelican client transparently alongside their web browser, without requiring that websites have the Pelican server installed. Likewise, a website operator can run the Pelican server without requiring that all clients run the Pelican client.

In the future, browsers could internalize the Pelican client to provide the protocol without needing the separate installation of the Pelican client proxy. In the future, popular webservers could incorporate the Pelican server protocol to make it even easer to deploy. Once Pelican-enabled clients and servers become sufficiently popular and prevalent, websites could then start to choose to provide a Phishing-free, Pelican-only option. For now, these proxies are used to provide the protocol with one-time only installation.

Technical question

Is the server's hostkey bound to a particular domain name suffix or IP address, as in TLS certificates?

A: Nope. The private RSA key that identifies the server can be moved, backed-up, and restored onto a different IP address. Does this mean that if my private keys are stolen, then my site can be impersonated? Absolutely. Protect your keys with a strong passphrase, and use the auto-backup so you don't loose them.

The advantage here is two fold: its simpler, and it still prevents phishing attacks. If your first use is compromised you are in trouble no matter what, so lets keep administration and use easy.

Documentation

Index

Constants

View Source
const AddIfNotKnown = true
View Source
const IgnoreIfNotKnown = false
View Source
const RequiredSaltLen = 64

Variables

View Source
var DockerHubTestImage string = "jaten/pelican04"

this is the current docker image used to run sshd inside of and connect to. We don't actually connect to a real sshd anymore, but we did during protocol development. It might be useful to test against the real sshd in the future, so we keep it around.

View Source
var OriginatorPrivateKey string = `` /* 3247-byte string literal not displayed */
View Source
var OriginatorPublicKey string = `` /* 724-byte string literal not displayed */
View Source
var Verbose bool

Verbose can be set to true for debug output. For production builds it should be set to false, the default.

Functions

func CleanupOldKnownHosts

func CleanupOldKnownHosts(fn string)

CleanupOldKnownHosts removes fn + defaultFileFormat().

func DecryptAes256Gcm

func DecryptAes256Gcm(passphrase []byte, cryptoText []byte) []byte

DecryptAes256Gcm is the inverse of EncryptAesGcm. It removes the base64url encoding, and then decrypts cryptoText using passphrase under the assumption that AES256-GCM was used to encrypt it.

func DirExists

func DirExists(name string) bool

DirExists returns true if name represents a directory on disk.

func EncryptAes256Gcm

func EncryptAes256Gcm(passphrase []byte, plaintext []byte, salt []byte) []byte

EncryptAes256Gcm encrypts plaintext using passphrase using AES256-GCM, then converts it to base64url encoding.

func FileExists

func FileExists(name string) bool

FileExists returns true iff the path name is a file (and not a directory or non-existant).

func FileExistsLen

func FileExistsLen(name string) (bool, int64)

FileExistsLen check if name is an actually file (directories don't count) and also returns the length of the file.

func Fingerprint

func Fingerprint(k ssh.PublicKey) string

Fingerprint performs a SHA256 BASE64 fingerprint of the PublicKey, similar to OpenSSH. See: https://anongit.mindrot.org/openssh.git/commit/?id=56d1c83cdd1ac

func GenAddress

func GenAddress() string

GenAddress generates a local address by calling GetAvailPort() and GetExternalIP(), then prefixing them with 'tcp://'.

func GenRsaKeyPair

func GenRsaKeyPair(rsa_file string, bits int) (priv *rsa.PrivateKey, sshPriv ssh.Signer, err error)

GenRsaKeyPair generates an RSA keypair of length bits. If rsa_file != "", we write the private key to rsa_file and the public key to rsa_file + ".pub". If rsa_file == "" the keys are not written to disk.

func GetAvailPort

func GetAvailPort() int

GetAvailPort asks the OS for an unused port. There's a race here, where the port could be grabbed by someone else before the caller gets to Listen on it, but in practice such races are rare. Uses net.Listen("tcp", ":0") to determine a free port, then releases it back to the OS with Listener.Close().

func GetDockerIP

func GetDockerIP() string

GetDockerIP returns the IP address bound by the container returned by RunningDockerId().

func GetExternalIP

func GetExternalIP() string

GetExternalIP tries to determine the external IP address used on this host.

func GetExternalIPAsInt

func GetExternalIPAsInt() int

GetExternalIPAsInt calls GetExternalIP() and then converts the resulting IPv4 string into an integer.

func GetOriginatorPrivateKey

func GetOriginatorPrivateKey() string

func GetOriginatorPublicKey

func GetOriginatorPublicKey() string

func GzipFile

func GzipFile(ungzipped, gzipped string) error

GzipFile reads the file in path ungzipped, then writes it back out to path gzipped in compressed format.

func IsRoutableIPv4

func IsRoutableIPv4(ip string) bool

IsRoutableIPv4 returns true if the string in ip represents an IPv4 address that is not private. See http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces for the numeric ranges that are private. 127.0.0.1, 192.168.0.1, and 172.16.0.1 are examples of non-routables IP addresses.

func KnownHostsEqual

func KnownHostsEqual(a, b *KnownHosts) (bool, error)

KnownHostsEqual compares two instances of KnownHosts structures for equality.

func LoadRSAPrivateKey

func LoadRSAPrivateKey(path string) (privkey ssh.Signer, err error)

LoadRSAPrivateKey reads a private key from path on disk.

func LoadRSAPublicKey

func LoadRSAPublicKey(path string) (pubkey ssh.PublicKey, err error)

LoadRSAPublicKey reads a public key from path on disk. By convention these keys end in '.pub', but that is not verified here.

func MakeRandPadding

func MakeRandPadding(minBytes int, maxBytes int) []byte

MakeRandPadding produces non crypto (fast) random bytes for prepending to messges/compressed messages to avoid leaking info, and to make it harder to recognize if you've actually cracked it.

func NewMyClient

func NewMyClient(c ssh.Conn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request) *myClient

func PasswordToSshPrivKeyUnlocker

func PasswordToSshPrivKeyUnlocker(password []byte, iv []byte) []byte

PasswordToSshPrivKeyUnlocker follows the OpenSSL formula for converting a human entered password into the key used to decode the AES-128-CBC (or otherwise) encrypted private key.

func ReadGzippedFile

func ReadGzippedFile(gzipped string) ([]byte, error)

ReadGzippedFile reads from path gzipped, returning the uncompressed bytes.

func RemoveAsciiArmor

func RemoveAsciiArmor(data []byte) ([]byte, error)

RemoveAsciiArmor is the inverse of WrapInAsciiArmor. It removes the armor from data.

func RsaToSshPublicKey

func RsaToSshPublicKey(pubkey *rsa.PublicKey) []byte

RsaToSshPublicKey convert an RSA Public Key to the SSH authorized_keys format.

func RunningDockerId

func RunningDockerId() ([]byte, error)

RunningDockerId runs 'docker ps -q -n=1 -f status=running' and returns the output and any error.

func StartDockerImage

func StartDockerImage(image string)

StartDockerImage starts a docker container based on image. It runs /sbin/my_init as the process, thereby assuming that /sbin/my_init is available inside the named image.

func StopAllDockers

func StopAllDockers()

StopAllDockers calls 'docker stop' on all containers determined by successive calls to RunningDockerId().

func StripNanomsgAddressPrefix

func StripNanomsgAddressPrefix(nanomsgAddr string) (suffix string, err error)

StripNanomsgAddressPrefix removes the 'tcp://' prefix from nanomsgAddr.

func TSPrintf

func TSPrintf(format string, a ...interface{})

time-stamped fmt.Printf

func TrimRightNewline

func TrimRightNewline(slice []byte) []byte

TrimRightNewline removes the trailing byte of slice if it is a newline '\n' character.

func Ts

func Ts() string

Ts gets the current timestamp for logging purposes.

func UnGzipFile

func UnGzipFile(gzipped, ungzipped string) error

UnGzipFile reads the file gzipped into memory, and the writes it back out to disk in file ungzipped without the compression.

func VPrintf

func VPrintf(format string, a ...interface{})

VPrintf is like fmt.Printf, but only prints if Verbose is true. Uses TSPrint to mark each print with a timestamp.

func WrapInAsciiArmor

func WrapInAsciiArmor(data []byte) ([]byte, error)

WrapInAsciiArmor returns data as an Ascii-armored text block, using the type PELICAN-PROTOCOL-FORMAT. This makes its contents more resilliant to being forwarded through email.

func WriteGzippedFile

func WriteGzippedFile(by []byte, gzipped string) error

WriteGzippedFile writes the bytes in by to the file named by gzipped.

func XorWrapBytes

func XorWrapBytes(a []byte, b []byte) []byte

XorWrapBytes deterministicallyl XORs two byte slices together, wrapping one against the other if need be. The result is the same length as the longer of a and b

Types

type HostState

type HostState int
const Banned HostState = 1
const KnownOK HostState = 2
const KnownRecordMismatch HostState = 3
const Unknown HostState = 0

func (HostState) String

func (s HostState) String() string

type KnownHosts

type KnownHosts struct {
	Hosts map[string]*ServerPubKey

	// FilepathPrefix doesn't have the .json.snappy suffix on it.
	FilepathPrefix string

	// PersistFormat doubles as the file suffix as well as
	// the format indicator
	PersistFormat string
	// contains filtered or unexported fields
}

KnownHosts represents in Hosts a hash map of host identifier (ip or name) and the corresponding public key for the server. It corresponds to the ~/.ssh/known_hosts file.

func NewKnownHosts

func NewKnownHosts(filepathPrefix string) *KnownHosts

NewKnownHosts creats a new KnownHosts structure. filepathPrefix does not include the

PersistFormat suffix. If filepathPrefix + defaultFileFormat() exists as a

file on disk, then we read the contents of that file into the new KnownHosts. We note the filepathPrefix for future saves back to that file as well.

func (*KnownHosts) Close

func (h *KnownHosts) Close()

Close cleans up and prepares for shutdown. It calls h.Sync() to write the state to disk.

func (*KnownHosts) HostAlreadyKnown

func (h *KnownHosts) HostAlreadyKnown(hostname string, remote net.Addr, key ssh.PublicKey, pubBytes []byte, addIfNotKnown bool) (HostState, error, *ServerPubKey)

func (*KnownHosts) SshConnect

func (h *KnownHosts) SshConnect(username string, keypath string, host string, port int, localPortToListenOn int) ([]byte, error)

func (*KnownHosts) SshMakeNewAcct

func (h *KnownHosts) SshMakeNewAcct(privKeyPath string, host string, port int) (acctid string, err error)

func (*KnownHosts) Sync

func (h *KnownHosts) Sync()

Sync writes the contents of the KnownHosts structure to the file h.FilepathPrefix + h.PersistFormat.

type MailgunConfig

type MailgunConfig struct {
	ApiKey     string   `json:"apikey"`
	Domain     string   `json:"domain"`
	FromEmail  string   `json:"from-email"`  // e.g. "Jill McMail <jill@example.com>"
	RecipEmail []string `json:"recip-email"` // e.g. ["Jill McMail <jill@examaple.com", "Joe McMail <joe@example.com>"]
}

func ReadMailgunConfig

func ReadMailgunConfig(path string) *MailgunConfig

func (*MailgunConfig) Load

func (c *MailgunConfig) Load(path string) error

func (*MailgunConfig) Save

func (c *MailgunConfig) Save(path string) error

func (*MailgunConfig) SendPPPBackupMailgunMail

func (cfg *MailgunConfig) SendPPPBackupMailgunMail(body string) (statusMsg string, id string, err error)

type ServerPubKey

type ServerPubKey struct {
	Hostname string

	// HumanKey is a serialized and readable version of Key, the key for Hosts map in KnownHosts.
	HumanKey     string
	ServerBanned bool
	// contains filtered or unexported fields
}

ServerPublicKey stores the RSA public keys for a particular known server. This structure is stored in KnownHosts.Hosts.

type Shovel

type Shovel struct {
	Done    chan bool
	ReqStop chan bool
	Ready   chan bool
}

Shovel shovels data from an io.ReadCloser to an io.WriteCloser in an independent go routine started by Shovel::Start(). You can request that the shovel stop by closing ReqStop, and wait until Done is closed to know that it is finished.

func NewShovel

func NewShovel() *Shovel

make a new Shovel

func (*Shovel) Start

func (s *Shovel) Start(w io.WriteCloser, r io.ReadCloser, label string)

Start starts the shovel doing an io.Copy from r to w. The goroutine that is running the copy will close the Ready channel just before starting the io.Copy. The label parameter allows reporting on when a specific shovel was shut down.

func (*Shovel) Stop

func (s *Shovel) Stop()

stop the shovel goroutine. returns only once the goroutine is done.

type ShovelPair

type ShovelPair struct {
	AB      *Shovel
	BA      *Shovel
	Done    chan bool
	ReqStop chan bool
	Ready   chan bool
}

a ShovelPair manages the forwarding of a bidirectional channel, such as that in forwarding an ssh connection.

func NewShovelPair

func NewShovelPair() *ShovelPair

make a new ShovelPair

func (*ShovelPair) Start

func (s *ShovelPair) Start(a io.ReadWriteCloser, b io.ReadWriteCloser, ab_label string, ba_label string)

Start the pair of shovels. ab_label will label the a<-b shovel. ba_label will label the b<-a shovel.

func (*ShovelPair) Stop

func (s *ShovelPair) Stop()

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL