switch to go module

This commit is contained in:
ginuerzh 2018-12-07 00:23:31 +08:00
parent 630b9f87b8
commit ed271ec258
1037 changed files with 61 additions and 342235 deletions

38
go.mod Normal file
View File

@ -0,0 +1,38 @@
module github.com/ginuerzh/gost
require (
git.torproject.org/pluggable-transports/goptlib.git v0.0.0-20180321061416-7d56ec4f381e
git.torproject.org/pluggable-transports/obfs4.git v0.0.0-20181103133120-08f4d470188e
github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63 // indirect
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861 // indirect
github.com/cheekybits/genny v1.0.0 // indirect
github.com/dchest/siphash v1.2.1 // indirect
github.com/ginuerzh/gosocks4 v0.0.1
github.com/ginuerzh/gosocks5 v0.2.0
github.com/ginuerzh/tls-dissector v0.0.1
github.com/go-log/log v0.1.0
github.com/gobwas/glob v0.2.3
github.com/hashicorp/golang-lru v0.5.0 // indirect
github.com/klauspost/compress v1.4.1
github.com/klauspost/cpuid v1.2.0 // indirect
github.com/klauspost/crc32 v1.2.0 // indirect
github.com/klauspost/reedsolomon v1.7.0 // indirect
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f // indirect
github.com/lucas-clemente/quic-go v0.10.0
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced // indirect
github.com/miekg/dns v1.1.1
github.com/pkg/errors v0.8.0 // indirect
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735
github.com/shadowsocks/shadowsocks-go v0.0.0-20170121203516-97a5c71f80ba
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b // indirect
github.com/tjfoc/gmsm v1.0.1 // indirect
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc
golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e // indirect
golang.org/x/text v0.3.0 // indirect
gopkg.in/gorilla/websocket.v1 v1.4.0
gopkg.in/xtaci/kcp-go.v4 v4.3.2
gopkg.in/xtaci/smux.v1 v1.0.7
)

35
kcp.go
View File

@ -15,7 +15,7 @@ import (
"github.com/go-log/log"
"github.com/klauspost/compress/snappy"
"gopkg.in/xtaci/kcp-go.v2"
"gopkg.in/xtaci/kcp-go.v4"
"gopkg.in/xtaci/smux.v1"
)
@ -52,15 +52,13 @@ type KCPConfig struct {
func (c *KCPConfig) Init() {
switch c.Mode {
case "normal":
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 0, 50, 2, 1
case "fast2":
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 30, 2, 1
case "fast3":
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 20, 2, 1
case "fast":
fallthrough
default:
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 0, 40, 2, 1
case "fast":
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 0, 30, 2, 1
case "fast2":
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 20, 2, 1
case "fast3":
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 10, 2, 1
}
}
@ -177,18 +175,17 @@ func (tr *kcpTransporter) initSession(addr string, conn net.Conn, config *KCPCon
kcpconn, err := kcp.NewConn(addr,
blockCrypt(config.Key, config.Crypt, KCPSalt),
config.DataShard, config.ParityShard,
&kcp.ConnectedUDPConn{UDPConn: udpConn, Conn: udpConn})
config.DataShard, config.ParityShard, &connectedUDPConn{udpConn})
if err != nil {
return nil, err
}
kcpconn.SetStreamMode(true)
kcpconn.SetWriteDelay(false)
kcpconn.SetNoDelay(config.NoDelay, config.Interval, config.Resend, config.NoCongestion)
kcpconn.SetWindowSize(config.SndWnd, config.RcvWnd)
kcpconn.SetMtu(config.MTU)
kcpconn.SetACKNoDelay(config.AckNodelay)
kcpconn.SetKeepAlive(config.KeepAlive)
// if err := kcpconn.SetDSCP(config.DSCP); err != nil {
// log.Log("[kcp]", err)
@ -203,6 +200,7 @@ func (tr *kcpTransporter) initSession(addr string, conn net.Conn, config *KCPCon
// stream multiplex
smuxConfig := smux.DefaultConfig()
smuxConfig.MaxReceiveBuffer = config.SockBuf
smuxConfig.KeepAliveInterval = time.Duration(config.KeepAlive) * time.Second
var cc net.Conn = kcpconn
if !config.NoComp {
cc = newCompStreamConn(kcpconn)
@ -274,11 +272,11 @@ func (l *kcpListener) listenLoop() {
return
}
conn.SetStreamMode(true)
conn.SetWriteDelay(false)
conn.SetNoDelay(l.config.NoDelay, l.config.Interval, l.config.Resend, l.config.NoCongestion)
conn.SetMtu(l.config.MTU)
conn.SetWindowSize(l.config.SndWnd, l.config.RcvWnd)
conn.SetACKNoDelay(l.config.AckNodelay)
conn.SetKeepAlive(l.config.KeepAlive)
go l.mux(conn)
}
}
@ -286,6 +284,7 @@ func (l *kcpListener) listenLoop() {
func (l *kcpListener) mux(conn net.Conn) {
smuxConfig := smux.DefaultConfig()
smuxConfig.MaxReceiveBuffer = l.config.SockBuf
smuxConfig.KeepAliveInterval = time.Duration(l.config.KeepAlive) * time.Second
log.Logf("[kcp] %s - %s", conn.RemoteAddr(), l.Addr())
@ -343,6 +342,8 @@ func blockCrypt(key, crypt, salt string) (block kcp.BlockCrypt) {
pass := pbkdf2.Key([]byte(key), []byte(salt), 4096, 32, sha1.New)
switch crypt {
case "sm4":
block, _ = kcp.NewSM4BlockCrypt(pass[:16])
case "tea":
block, _ = kcp.NewTEABlockCrypt(pass[:16])
case "xor":
@ -451,3 +452,11 @@ func (c *compStreamConn) SetReadDeadline(t time.Time) error {
func (c *compStreamConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
}
// connectedUDPConn is a wrapper for net.UDPConn which converts WriteTo syscalls
// to Write syscalls that are 4 times faster on some OS'es. This should only be
// used for connections that were produced by a net.Dial* call.
type connectedUDPConn struct{ *net.UDPConn }
// WriteTo redirects all writes to the Write syscall, which is 4 times faster.
func (c *connectedUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) { return c.Write(b) }

View File

@ -8,7 +8,7 @@ import (
"syscall"
"github.com/go-log/log"
"gopkg.in/xtaci/kcp-go.v2"
"gopkg.in/xtaci/kcp-go.v4"
)
func kcpSigHandler() {

View File

@ -1,121 +0,0 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

View File

@ -1,27 +0,0 @@
goptlib is a library for writing Tor pluggable transports in Go.
https://spec.torproject.org/pt-spec
https://gitweb.torproject.org/torspec.git/tree/proposals/196-transport-control-ports.txt
https://gitweb.torproject.org/torspec.git/tree/proposals/217-ext-orport-auth.txt
https://gitweb.torproject.org/torspec.git/tree/proposals/232-pluggable-transports-through-proxy.txt
To download a copy of the library into $GOPATH:
go get git.torproject.org/pluggable-transports/goptlib.git
See the included example programs for examples of how to use the
library. To build them, enter their directory and run "go build".
examples/dummy-client/dummy-client.go
examples/dummy-server/dummy-server.go
The recommended way to start writing a new transport plugin is to copy
dummy-client or dummy-server and make changes to it.
There is browseable documentation here:
https://godoc.org/git.torproject.org/pluggable-transports/goptlib.git
Report bugs to the tor-dev@lists.torproject.org mailing list or to the
bug tracker at https://trac.torproject.org/projects/tor.
To the extent possible under law, the authors have dedicated all
copyright and related and neighboring rights to this software to the
public domain worldwide. This software is distributed without any
warranty. See COPYING.

View File

@ -1,219 +0,0 @@
package pt
import (
"bytes"
"fmt"
"sort"
"strings"
)
// Keyvalue mappings for the representation of client and server options.
// Args maps a string key to a list of values. It is similar to url.Values.
type Args map[string][]string
// Get the first value associated with the given key. If there are any values
// associated with the key, the value return has the value and ok is set to
// true. If there are no values for the given key, value is "" and ok is false.
// If you need access to multiple values, use the map directly.
func (args Args) Get(key string) (value string, ok bool) {
if args == nil {
return "", false
}
vals, ok := args[key]
if !ok || len(vals) == 0 {
return "", false
}
return vals[0], true
}
// Append value to the list of values for key.
func (args Args) Add(key, value string) {
args[key] = append(args[key], value)
}
// Return the index of the next unescaped byte in s that is in the term set, or
// else the length of the string if no terminators appear. Additionally return
// the unescaped string up to the returned index.
func indexUnescaped(s string, term []byte) (int, string, error) {
var i int
unesc := make([]byte, 0)
for i = 0; i < len(s); i++ {
b := s[i]
// A terminator byte?
if bytes.IndexByte(term, b) != -1 {
break
}
if b == '\\' {
i++
if i >= len(s) {
return 0, "", fmt.Errorf("nothing following final escape in %q", s)
}
b = s[i]
}
unesc = append(unesc, b)
}
return i, string(unesc), nil
}
// Parse a namevalue mapping as from an encoded SOCKS username/password.
//
// "First the '<Key>=<Value>' formatted arguments MUST be escaped, such that all
// backslash, equal sign, and semicolon characters are escaped with a
// backslash."
func parseClientParameters(s string) (args Args, err error) {
args = make(Args)
if len(s) == 0 {
return
}
i := 0
for {
var key, value string
var offset, begin int
begin = i
// Read the key.
offset, key, err = indexUnescaped(s[i:], []byte{'=', ';'})
if err != nil {
return
}
i += offset
// End of string or no equals sign?
if i >= len(s) || s[i] != '=' {
err = fmt.Errorf("no equals sign in %q", s[begin:i])
return
}
// Skip the equals sign.
i++
// Read the value.
offset, value, err = indexUnescaped(s[i:], []byte{';'})
if err != nil {
return
}
i += offset
if len(key) == 0 {
err = fmt.Errorf("empty key in %q", s[begin:i])
return
}
args.Add(key, value)
if i >= len(s) {
break
}
// Skip the semicolon.
i++
}
return args, nil
}
// Parse a transportnamevalue mapping as from TOR_PT_SERVER_TRANSPORT_OPTIONS.
//
// "...a semicolon-separated list of <key>:<value> pairs, where <key> is a PT
// name and <value> is a k=v string value with options that are to be passed to
// the transport. Colons, semicolons, equal signs and backslashes must be
// escaped with a backslash."
// Example: scramblesuit:key=banana;automata:rule=110;automata:depth=3
func parseServerTransportOptions(s string) (opts map[string]Args, err error) {
opts = make(map[string]Args)
if len(s) == 0 {
return
}
i := 0
for {
var methodName, key, value string
var offset, begin int
begin = i
// Read the method name.
offset, methodName, err = indexUnescaped(s[i:], []byte{':', '=', ';'})
if err != nil {
return
}
i += offset
// End of string or no colon?
if i >= len(s) || s[i] != ':' {
err = fmt.Errorf("no colon in %q", s[begin:i])
return
}
// Skip the colon.
i++
// Read the key.
offset, key, err = indexUnescaped(s[i:], []byte{'=', ';'})
if err != nil {
return
}
i += offset
// End of string or no equals sign?
if i >= len(s) || s[i] != '=' {
err = fmt.Errorf("no equals sign in %q", s[begin:i])
return
}
// Skip the equals sign.
i++
// Read the value.
offset, value, err = indexUnescaped(s[i:], []byte{';'})
if err != nil {
return
}
i += offset
if len(methodName) == 0 {
err = fmt.Errorf("empty method name in %q", s[begin:i])
return
}
if len(key) == 0 {
err = fmt.Errorf("empty key in %q", s[begin:i])
return
}
if opts[methodName] == nil {
opts[methodName] = make(Args)
}
opts[methodName].Add(key, value)
if i >= len(s) {
break
}
// Skip the semicolon.
i++
}
return opts, nil
}
// Escape backslashes and all the bytes that are in set.
func backslashEscape(s string, set []byte) string {
var buf bytes.Buffer
for _, b := range []byte(s) {
if b == '\\' || bytes.IndexByte(set, b) != -1 {
buf.WriteByte('\\')
}
buf.WriteByte(b)
}
return buf.String()
}
// Encode a namevalue mapping so that it is suitable to go in the ARGS option
// of an SMETHOD line. The output is sorted by key. The "ARGS:" prefix is not
// added.
//
// "Equal signs and commas [and backslashes] MUST be escaped with a backslash."
func encodeSmethodArgs(args Args) string {
if args == nil {
return ""
}
keys := make([]string, 0, len(args))
for key := range args {
keys = append(keys, key)
}
sort.Strings(keys)
escape := func(s string) string {
return backslashEscape(s, []byte{'=', ','})
}
var pairs []string
for _, key := range keys {
for _, value := range args[key] {
pairs = append(pairs, escape(key)+"="+escape(value))
}
}
return strings.Join(pairs, ",")
}

View File

@ -1,949 +0,0 @@
// Package pt implements the Tor pluggable transports specification.
//
// Sample client usage:
// var ptInfo pt.ClientInfo
// ...
// func handler(conn *pt.SocksConn) error {
// defer conn.Close()
// remote, err := net.Dial("tcp", conn.Req.Target)
// if err != nil {
// conn.Reject()
// return err
// }
// defer remote.Close()
// err = conn.Grant(remote.RemoteAddr().(*net.TCPAddr))
// if err != nil {
// return err
// }
// // do something with conn and remote.
// return nil
// }
// func acceptLoop(ln *pt.SocksListener) error {
// defer ln.Close()
// for {
// conn, err := ln.AcceptSocks()
// if err != nil {
// if e, ok := err.(net.Error); ok && e.Temporary() {
// continue
// }
// return err
// }
// go handler(conn)
// }
// return nil
// }
// ...
// func main() {
// var err error
// ptInfo, err = pt.ClientSetup(nil)
// if err != nil {
// os.Exit(1)
// }
// if ptInfo.ProxyURL != nil {
// // you need to interpret the proxy URL yourself
// // call pt.ProxyDone instead if it's a type you understand
// pt.ProxyError(fmt.Sprintf("proxy %s is not supported", ptInfo.ProxyURL))
// os.Exit(1)
// }
// for _, methodName := range ptInfo.MethodNames {
// switch methodName {
// case "foo":
// ln, err := pt.ListenSocks("tcp", "127.0.0.1:0")
// if err != nil {
// pt.CmethodError(methodName, err.Error())
// break
// }
// go acceptLoop(ln)
// pt.Cmethod(methodName, ln.Version(), ln.Addr())
// default:
// pt.CmethodError(methodName, "no such method")
// }
// }
// pt.CmethodsDone()
// }
//
// Sample server usage:
// var ptInfo pt.ServerInfo
// ...
// func handler(conn net.Conn) error {
// defer conn.Close()
// or, err := pt.DialOr(&ptInfo, conn.RemoteAddr().String(), "foo")
// if err != nil {
// return
// }
// defer or.Close()
// // do something with or and conn
// return nil
// }
// func acceptLoop(ln net.Listener) error {
// defer ln.Close()
// for {
// conn, err := ln.Accept()
// if err != nil {
// if e, ok := err.(net.Error); ok && e.Temporary() {
// continue
// }
// return err
// }
// go handler(conn)
// }
// return nil
// }
// ...
// func main() {
// var err error
// ptInfo, err = pt.ServerSetup(nil)
// if err != nil {
// os.Exit(1)
// }
// for _, bindaddr := range ptInfo.Bindaddrs {
// switch bindaddr.MethodName {
// case "foo":
// ln, err := net.ListenTCP("tcp", bindaddr.Addr)
// if err != nil {
// pt.SmethodError(bindaddr.MethodName, err.Error())
// break
// }
// go acceptLoop(ln)
// pt.Smethod(bindaddr.MethodName, ln.Addr())
// default:
// pt.SmethodError(bindaddr.MethodName, "no such method")
// }
// }
// pt.SmethodsDone()
// }
//
// Some additional care is needed to handle signals and shutdown properly. See
// the example programs dummy-client and dummy-server.
//
// Tor pluggable transports specification:
// https://spec.torproject.org/pt-spec
//
// Extended ORPort:
// https://gitweb.torproject.org/torspec.git/tree/proposals/196-transport-control-ports.txt
//
// Extended ORPort Authentication:
// https://gitweb.torproject.org/torspec.git/tree/proposals/217-ext-orport-auth.txt
//
// Pluggable Transport through SOCKS proxy:
// https://gitweb.torproject.org/torspec.git/tree/proposals/232-pluggable-transports-through-proxy.txt
//
// The package implements a SOCKS5 server sufficient for a Tor client transport
// plugin.
//
// https://www.ietf.org/rfc/rfc1928.txt
// https://www.ietf.org/rfc/rfc1929.txt
package pt
import (
"bytes"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"crypto/subtle"
"encoding/binary"
"fmt"
"io"
"net"
"net/url"
"os"
"strconv"
"strings"
"time"
)
// This type wraps a Write method and calls Sync after each Write.
type syncWriter struct {
*os.File
}
// Call File.Write and then Sync. An error is returned if either operation
// returns an error.
func (w syncWriter) Write(p []byte) (n int, err error) {
n, err = w.File.Write(p)
if err != nil {
return
}
err = w.Sync()
return
}
// Writer to which pluggable transports negotiation messages are written. It
// defaults to a Writer that writes to os.Stdout and calls Sync after each
// write.
//
// You may, for example, log pluggable transports messages by defining a Writer
// that logs what is written to it:
// type logWriteWrapper struct {
// io.Writer
// }
//
// func (w logWriteWrapper) Write(p []byte) (int, error) {
// log.Print(string(p))
// return w.Writer.Write(p)
// }
// and then redefining Stdout:
// pt.Stdout = logWriteWrapper{pt.Stdout}
var Stdout io.Writer = syncWriter{os.Stdout}
// Represents an error that can happen during negotiation, for example
// ENV-ERROR. When an error occurs, we print it to stdout and also pass it up
// the return chain.
type ptErr struct {
Keyword string
Args []string
}
// Implements the error interface.
func (err *ptErr) Error() string {
return formatline(err.Keyword, err.Args...)
}
func getenv(key string) string {
return os.Getenv(key)
}
// Returns an ENV-ERROR if the environment variable isn't set.
func getenvRequired(key string) (string, error) {
value := os.Getenv(key)
if value == "" {
return "", envError(fmt.Sprintf("no %s environment variable", key))
}
return value, nil
}
// Returns true iff keyword contains only bytes allowed in a PT→Tor output line
// keyword.
// <KeywordChar> ::= <any US-ASCII alphanumeric, dash, and underscore>
func keywordIsSafe(keyword string) bool {
for _, b := range []byte(keyword) {
switch {
case '0' <= b && b <= '9':
continue
case 'A' <= b && b <= 'Z':
continue
case 'a' <= b && b <= 'z':
continue
case b == '-' || b == '_':
continue
default:
return false
}
}
return true
}
// Returns true iff arg contains only bytes allowed in a PT→Tor output line arg.
// <ArgChar> ::= <any US-ASCII character but NUL or NL>
func argIsSafe(arg string) bool {
for _, b := range []byte(arg) {
if b >= '\x80' || b == '\x00' || b == '\n' {
return false
}
}
return true
}
func formatline(keyword string, v ...string) string {
var buf bytes.Buffer
if !keywordIsSafe(keyword) {
panic(fmt.Sprintf("keyword %q contains forbidden bytes", keyword))
}
buf.WriteString(keyword)
for _, x := range v {
if !argIsSafe(x) {
panic(fmt.Sprintf("arg %q contains forbidden bytes", x))
}
buf.WriteString(" " + x)
}
return buf.String()
}
// Print a pluggable transports protocol line to Stdout. The line consists of a
// keyword followed by any number of space-separated arg strings. Panics if
// there are forbidden bytes in the keyword or the args (pt-spec.txt 2.2.1).
func line(keyword string, v ...string) {
fmt.Fprintln(Stdout, formatline(keyword, v...))
}
// Emit and return the given error as a ptErr.
func doError(keyword string, v ...string) *ptErr {
line(keyword, v...)
return &ptErr{keyword, v}
}
// Emit an ENV-ERROR line with explanation text. Returns a representation of the
// error.
func envError(msg string) error {
return doError("ENV-ERROR", msg)
}
// Emit a VERSION-ERROR line with explanation text. Returns a representation of
// the error.
func versionError(msg string) error {
return doError("VERSION-ERROR", msg)
}
// Emit a CMETHOD-ERROR line with explanation text. Returns a representation of
// the error.
func CmethodError(methodName, msg string) error {
return doError("CMETHOD-ERROR", methodName, msg)
}
// Emit an SMETHOD-ERROR line with explanation text. Returns a representation of
// the error.
func SmethodError(methodName, msg string) error {
return doError("SMETHOD-ERROR", methodName, msg)
}
// Emit a PROXY-ERROR line with explanation text. Returns a representation of
// the error.
func ProxyError(msg string) error {
return doError("PROXY-ERROR", msg)
}
// Emit a CMETHOD line. socks must be "socks4" or "socks5". Call this once for
// each listening client SOCKS port.
func Cmethod(name string, socks string, addr net.Addr) {
line("CMETHOD", name, socks, addr.String())
}
// Emit a CMETHODS DONE line. Call this after opening all client listeners.
func CmethodsDone() {
line("CMETHODS", "DONE")
}
// Emit an SMETHOD line. Call this once for each listening server port.
func Smethod(name string, addr net.Addr) {
line("SMETHOD", name, addr.String())
}
// Emit an SMETHOD line with an ARGS option. args is a namevalue mapping that
// will be added to the server's extrainfo document.
//
// This is an example of how to check for a required option:
// secret, ok := bindaddr.Options.Get("shared-secret")
// if ok {
// args := pt.Args{}
// args.Add("shared-secret", secret)
// pt.SmethodArgs(bindaddr.MethodName, ln.Addr(), args)
// } else {
// pt.SmethodError(bindaddr.MethodName, "need a shared-secret option")
// }
// Or, if you just want to echo back the options provided by Tor from the
// TransportServerOptions configuration,
// pt.SmethodArgs(bindaddr.MethodName, ln.Addr(), bindaddr.Options)
func SmethodArgs(name string, addr net.Addr, args Args) {
line("SMETHOD", name, addr.String(), "ARGS:"+encodeSmethodArgs(args))
}
// Emit an SMETHODS DONE line. Call this after opening all server listeners.
func SmethodsDone() {
line("SMETHODS", "DONE")
}
// Emit a PROXY DONE line. Call this after parsing ClientInfo.ProxyURL.
func ProxyDone() {
fmt.Fprintf(Stdout, "PROXY DONE\n")
}
// Get a pluggable transports version offered by Tor and understood by us, if
// any. The only version we understand is "1". This function reads the
// environment variable TOR_PT_MANAGED_TRANSPORT_VER.
func getManagedTransportVer() (string, error) {
const transportVersion = "1"
managedTransportVer, err := getenvRequired("TOR_PT_MANAGED_TRANSPORT_VER")
if err != nil {
return "", err
}
for _, offered := range strings.Split(managedTransportVer, ",") {
if offered == transportVersion {
return offered, nil
}
}
return "", versionError("no-version")
}
// Return the directory name in the TOR_PT_STATE_LOCATION environment variable,
// creating it if it doesn't exist. Returns non-nil error if
// TOR_PT_STATE_LOCATION is not set or if there is an error creating the
// directory.
func MakeStateDir() (string, error) {
dir, err := getenvRequired("TOR_PT_STATE_LOCATION")
if err != nil {
return "", err
}
err = os.MkdirAll(dir, 0700)
return dir, err
}
// Get the list of method names requested by Tor. This function reads the
// environment variable TOR_PT_CLIENT_TRANSPORTS.
func getClientTransports() ([]string, error) {
clientTransports, err := getenvRequired("TOR_PT_CLIENT_TRANSPORTS")
if err != nil {
return nil, err
}
return strings.Split(clientTransports, ","), nil
}
// Get the upstream proxy URL. Returns nil if no proxy is requested. The
// function ensures that the Scheme and Host fields are set; i.e., that the URL
// is absolute. It additionally checks that the Host field contains both a host
// and a port part. This function reads the environment variable TOR_PT_PROXY.
//
// This function doesn't check that the scheme is one of Tor's supported proxy
// schemes; that is, one of "http", "socks5", or "socks4a". The caller must be
// able to handle any returned scheme (which may be by calling ProxyError if
// it doesn't know how to handle the scheme).
func getProxyURL() (*url.URL, error) {
rawurl := os.Getenv("TOR_PT_PROXY")
if rawurl == "" {
return nil, nil
}
u, err := url.Parse(rawurl)
if err != nil {
return nil, err
}
if u.Scheme == "" {
return nil, fmt.Errorf("missing scheme")
}
if u.Host == "" {
return nil, fmt.Errorf("missing authority")
}
host, port, err := net.SplitHostPort(u.Host)
if err != nil {
return nil, err
}
if host == "" {
return nil, fmt.Errorf("missing host")
}
if port == "" {
return nil, fmt.Errorf("missing port")
}
return u, nil
}
// This structure is returned by ClientSetup. It consists of a list of method
// names and the upstream proxy URL, if any.
type ClientInfo struct {
MethodNames []string
ProxyURL *url.URL
}
// Check the client pluggable transports environment, emitting an error message
// and returning a non-nil error if any error is encountered. Returns a
// ClientInfo struct.
//
// If your program needs to know whether to call ClientSetup or ServerSetup
// (i.e., if the same program can be run as either a client or a server), check
// whether the TOR_PT_CLIENT_TRANSPORTS environment variable is set:
// if os.Getenv("TOR_PT_CLIENT_TRANSPORTS") != "" {
// // Client mode; call pt.ClientSetup.
// } else {
// // Server mode; call pt.ServerSetup.
// }
//
// Always pass nil for the unused single parameter. In the past, the parameter
// was a list of transport names to use in case Tor requested "*". That feature
// was never implemented and has been removed from the pluggable transports
// specification.
// https://bugs.torproject.org/15612
func ClientSetup(_ []string) (info ClientInfo, err error) {
ver, err := getManagedTransportVer()
if err != nil {
return
}
line("VERSION", ver)
info.MethodNames, err = getClientTransports()
if err != nil {
return
}
info.ProxyURL, err = getProxyURL()
if err != nil {
return
}
return info, nil
}
// A combination of a method name and an address, as extracted from
// TOR_PT_SERVER_BINDADDR.
type Bindaddr struct {
MethodName string
Addr *net.TCPAddr
// Options from TOR_PT_SERVER_TRANSPORT_OPTIONS that pertain to this
// transport.
Options Args
}
func parsePort(portStr string) (int, error) {
port, err := strconv.ParseUint(portStr, 10, 16)
return int(port), err
}
// Resolve an address string into a net.TCPAddr. We are a bit more strict than
// net.ResolveTCPAddr; we don't allow an empty host or port, and the host part
// must be a literal IP address.
func resolveAddr(addrStr string) (*net.TCPAddr, error) {
ipStr, portStr, err := net.SplitHostPort(addrStr)
if err != nil {
// Before the fixing of bug #7011, tor doesn't put brackets around IPv6
// addresses. Split after the last colon, assuming it is a port
// separator, and try adding the brackets.
// https://bugs.torproject.org/7011
parts := strings.Split(addrStr, ":")
if len(parts) <= 2 {
return nil, err
}
addrStr := "[" + strings.Join(parts[:len(parts)-1], ":") + "]:" + parts[len(parts)-1]
ipStr, portStr, err = net.SplitHostPort(addrStr)
}
if err != nil {
return nil, err
}
if ipStr == "" {
return nil, net.InvalidAddrError(fmt.Sprintf("address string %q lacks a host part", addrStr))
}
if portStr == "" {
return nil, net.InvalidAddrError(fmt.Sprintf("address string %q lacks a port part", addrStr))
}
ip := net.ParseIP(ipStr)
if ip == nil {
return nil, net.InvalidAddrError(fmt.Sprintf("not an IP string: %q", ipStr))
}
port, err := parsePort(portStr)
if err != nil {
return nil, err
}
return &net.TCPAddr{IP: ip, Port: port}, nil
}
// Return a new slice, the members of which are those members of addrs having a
// MethodName in methodNames.
func filterBindaddrs(addrs []Bindaddr, methodNames []string) []Bindaddr {
var result []Bindaddr
for _, ba := range addrs {
for _, methodName := range methodNames {
if ba.MethodName == methodName {
result = append(result, ba)
break
}
}
}
return result
}
// Return an array of Bindaddrs, being the contents of TOR_PT_SERVER_BINDADDR
// with keys filtered by TOR_PT_SERVER_TRANSPORTS. Transport-specific options
// from TOR_PT_SERVER_TRANSPORT_OPTIONS are assigned to the Options member.
func getServerBindaddrs() ([]Bindaddr, error) {
var result []Bindaddr
// Parse the list of server transport options.
serverTransportOptions := getenv("TOR_PT_SERVER_TRANSPORT_OPTIONS")
optionsMap, err := parseServerTransportOptions(serverTransportOptions)
if err != nil {
return nil, envError(fmt.Sprintf("TOR_PT_SERVER_TRANSPORT_OPTIONS: %q: %s", serverTransportOptions, err.Error()))
}
// Get the list of all requested bindaddrs.
serverBindaddr, err := getenvRequired("TOR_PT_SERVER_BINDADDR")
if err != nil {
return nil, err
}
seenMethods := make(map[string]bool)
for _, spec := range strings.Split(serverBindaddr, ",") {
var bindaddr Bindaddr
parts := strings.SplitN(spec, "-", 2)
if len(parts) != 2 {
return nil, envError(fmt.Sprintf("TOR_PT_SERVER_BINDADDR: %q: doesn't contain \"-\"", spec))
}
bindaddr.MethodName = parts[0]
// Check for duplicate method names: "Applications MUST NOT set
// more than one <address>:<port> pair per PT name."
if seenMethods[bindaddr.MethodName] {
return nil, envError(fmt.Sprintf("TOR_PT_SERVER_BINDADDR: %q: duplicate method name %q", spec, bindaddr.MethodName))
}
seenMethods[bindaddr.MethodName] = true
addr, err := resolveAddr(parts[1])
if err != nil {
return nil, envError(fmt.Sprintf("TOR_PT_SERVER_BINDADDR: %q: %s", spec, err.Error()))
}
bindaddr.Addr = addr
bindaddr.Options = optionsMap[bindaddr.MethodName]
result = append(result, bindaddr)
}
// Filter by TOR_PT_SERVER_TRANSPORTS.
serverTransports, err := getenvRequired("TOR_PT_SERVER_TRANSPORTS")
if err != nil {
return nil, err
}
result = filterBindaddrs(result, strings.Split(serverTransports, ","))
return result, nil
}
func readAuthCookie(f io.Reader) ([]byte, error) {
authCookieHeader := []byte("! Extended ORPort Auth Cookie !\x0a")
buf := make([]byte, 64)
n, err := io.ReadFull(f, buf)
if err != nil {
return nil, err
}
// Check that the file ends here.
n, err = f.Read(make([]byte, 1))
if n != 0 {
return nil, fmt.Errorf("file is longer than 64 bytes")
} else if err != io.EOF {
return nil, fmt.Errorf("did not find EOF at end of file")
}
header := buf[0:32]
cookie := buf[32:64]
if subtle.ConstantTimeCompare(header, authCookieHeader) != 1 {
return nil, fmt.Errorf("missing auth cookie header")
}
return cookie, nil
}
// Read and validate the contents of an auth cookie file. Returns the 32-byte
// cookie. See section 4.2.1.2 of 217-ext-orport-auth.txt.
func readAuthCookieFile(filename string) ([]byte, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
return readAuthCookie(f)
}
// This structure is returned by ServerSetup. It consists of a list of
// Bindaddrs, an address for the ORPort, an address for the extended ORPort (if
// any), and an authentication cookie (if any).
type ServerInfo struct {
Bindaddrs []Bindaddr
OrAddr *net.TCPAddr
ExtendedOrAddr *net.TCPAddr
AuthCookiePath string
}
// Check the server pluggable transports environment, emitting an error message
// and returning a non-nil error if any error is encountered. Resolves the
// various requested bind addresses, the server ORPort and extended ORPort, and
// reads the auth cookie file. Returns a ServerInfo struct.
//
// If your program needs to know whether to call ClientSetup or ServerSetup
// (i.e., if the same program can be run as either a client or a server), check
// whether the TOR_PT_CLIENT_TRANSPORTS environment variable is set:
// if os.Getenv("TOR_PT_CLIENT_TRANSPORTS") != "" {
// // Client mode; call pt.ClientSetup.
// } else {
// // Server mode; call pt.ServerSetup.
// }
//
// Always pass nil for the unused single parameter. In the past, the parameter
// was a list of transport names to use in case Tor requested "*". That feature
// was never implemented and has been removed from the pluggable transports
// specification.
// https://bugs.torproject.org/15612
func ServerSetup(_ []string) (info ServerInfo, err error) {
ver, err := getManagedTransportVer()
if err != nil {
return
}
line("VERSION", ver)
info.Bindaddrs, err = getServerBindaddrs()
if err != nil {
return
}
orPort := getenv("TOR_PT_ORPORT")
if orPort != "" {
info.OrAddr, err = resolveAddr(orPort)
if err != nil {
err = envError(fmt.Sprintf("cannot resolve TOR_PT_ORPORT %q: %s", orPort, err.Error()))
return
}
}
info.AuthCookiePath = getenv("TOR_PT_AUTH_COOKIE_FILE")
extendedOrPort := getenv("TOR_PT_EXTENDED_SERVER_PORT")
if extendedOrPort != "" {
if info.AuthCookiePath == "" {
err = envError("need TOR_PT_AUTH_COOKIE_FILE environment variable with TOR_PT_EXTENDED_SERVER_PORT")
return
}
info.ExtendedOrAddr, err = resolveAddr(extendedOrPort)
if err != nil {
err = envError(fmt.Sprintf("cannot resolve TOR_PT_EXTENDED_SERVER_PORT %q: %s", extendedOrPort, err.Error()))
return
}
}
// Need either OrAddr or ExtendedOrAddr.
if info.OrAddr == nil && info.ExtendedOrAddr == nil {
err = envError("need TOR_PT_ORPORT or TOR_PT_EXTENDED_SERVER_PORT environment variable")
return
}
return info, nil
}
// See 217-ext-orport-auth.txt section 4.2.1.3.
func computeServerHash(authCookie, clientNonce, serverNonce []byte) []byte {
h := hmac.New(sha256.New, authCookie)
io.WriteString(h, "ExtORPort authentication server-to-client hash")
h.Write(clientNonce)
h.Write(serverNonce)
return h.Sum([]byte{})
}
// See 217-ext-orport-auth.txt section 4.2.1.3.
func computeClientHash(authCookie, clientNonce, serverNonce []byte) []byte {
h := hmac.New(sha256.New, authCookie)
io.WriteString(h, "ExtORPort authentication client-to-server hash")
h.Write(clientNonce)
h.Write(serverNonce)
return h.Sum([]byte{})
}
func extOrPortAuthenticate(s io.ReadWriter, info *ServerInfo) error {
// Read auth types. 217-ext-orport-auth.txt section 4.1.
var authTypes [256]bool
var count int
for count = 0; count < 256; count++ {
buf := make([]byte, 1)
_, err := io.ReadFull(s, buf)
if err != nil {
return err
}
b := buf[0]
if b == 0 {
break
}
authTypes[b] = true
}
if count >= 256 {
return fmt.Errorf("read 256 auth types without seeing \\x00")
}
// We support only type 1, SAFE_COOKIE.
if !authTypes[1] {
return fmt.Errorf("server didn't offer auth type 1")
}
_, err := s.Write([]byte{1})
if err != nil {
return err
}
clientNonce := make([]byte, 32)
clientHash := make([]byte, 32)
serverNonce := make([]byte, 32)
serverHash := make([]byte, 32)
_, err = io.ReadFull(rand.Reader, clientNonce)
if err != nil {
return err
}
_, err = s.Write(clientNonce)
if err != nil {
return err
}
_, err = io.ReadFull(s, serverHash)
if err != nil {
return err
}
_, err = io.ReadFull(s, serverNonce)
if err != nil {
return err
}
// Work around tor bug #15240 where the auth cookie is generated after
// pluggable transports are launched, leading to a stale cookie getting
// cached forever if it is only read once as part of ServerSetup.
// https://bugs.torproject.org/15240
authCookie, err := readAuthCookieFile(info.AuthCookiePath)
if err != nil {
return fmt.Errorf("error reading TOR_PT_AUTH_COOKIE_FILE %q: %s", info.AuthCookiePath, err.Error())
}
expectedServerHash := computeServerHash(authCookie, clientNonce, serverNonce)
if subtle.ConstantTimeCompare(serverHash, expectedServerHash) != 1 {
return fmt.Errorf("mismatch in server hash")
}
clientHash = computeClientHash(authCookie, clientNonce, serverNonce)
_, err = s.Write(clientHash)
if err != nil {
return err
}
status := make([]byte, 1)
_, err = io.ReadFull(s, status)
if err != nil {
return err
}
if status[0] != 1 {
return fmt.Errorf("server rejected authentication")
}
return nil
}
// See section 3.1.1 of 196-transport-control-ports.txt.
const (
extOrCmdDone = 0x0000
extOrCmdUserAddr = 0x0001
extOrCmdTransport = 0x0002
extOrCmdOkay = 0x1000
extOrCmdDeny = 0x1001
)
func extOrPortSendCommand(s io.Writer, cmd uint16, body []byte) error {
var buf bytes.Buffer
if len(body) > 65535 {
return fmt.Errorf("body length %d exceeds maximum of 65535", len(body))
}
err := binary.Write(&buf, binary.BigEndian, cmd)
if err != nil {
return err
}
err = binary.Write(&buf, binary.BigEndian, uint16(len(body)))
if err != nil {
return err
}
err = binary.Write(&buf, binary.BigEndian, body)
if err != nil {
return err
}
_, err = s.Write(buf.Bytes())
if err != nil {
return err
}
return nil
}
// Send a USERADDR command on s. See section 3.1.2.1 of
// 196-transport-control-ports.txt.
func extOrPortSendUserAddr(s io.Writer, addr string) error {
return extOrPortSendCommand(s, extOrCmdUserAddr, []byte(addr))
}
// Send a TRANSPORT command on s. See section 3.1.2.2 of
// 196-transport-control-ports.txt.
func extOrPortSendTransport(s io.Writer, methodName string) error {
return extOrPortSendCommand(s, extOrCmdTransport, []byte(methodName))
}
// Send a DONE command on s. See section 3.1 of 196-transport-control-ports.txt.
func extOrPortSendDone(s io.Writer) error {
return extOrPortSendCommand(s, extOrCmdDone, []byte{})
}
func extOrPortRecvCommand(s io.Reader) (cmd uint16, body []byte, err error) {
var bodyLen uint16
data := make([]byte, 4)
_, err = io.ReadFull(s, data)
if err != nil {
return
}
buf := bytes.NewBuffer(data)
err = binary.Read(buf, binary.BigEndian, &cmd)
if err != nil {
return
}
err = binary.Read(buf, binary.BigEndian, &bodyLen)
if err != nil {
return
}
body = make([]byte, bodyLen)
_, err = io.ReadFull(s, body)
if err != nil {
return
}
return cmd, body, err
}
// Send USERADDR and TRANSPORT commands followed by a DONE command. Wait for an
// OKAY or DENY response command from the server. If addr or methodName is "",
// the corresponding command is not sent. Returns nil if and only if OKAY is
// received.
func extOrPortSetup(s io.ReadWriter, addr, methodName string) error {
var err error
if addr != "" {
err = extOrPortSendUserAddr(s, addr)
if err != nil {
return err
}
}
if methodName != "" {
err = extOrPortSendTransport(s, methodName)
if err != nil {
return err
}
}
err = extOrPortSendDone(s)
if err != nil {
return err
}
cmd, _, err := extOrPortRecvCommand(s)
if err != nil {
return err
}
if cmd == extOrCmdDeny {
return fmt.Errorf("server returned DENY after our USERADDR and DONE")
} else if cmd != extOrCmdOkay {
return fmt.Errorf("server returned unknown command 0x%04x after our USERADDR and DONE", cmd)
}
return nil
}
// Dial info.ExtendedOrAddr if defined, or else info.OrAddr, and return an open
// *net.TCPConn. If connecting to the extended OR port, extended OR port
// authentication à la 217-ext-orport-auth.txt is done before returning; an
// error is returned if authentication fails.
//
// The addr and methodName arguments are put in USERADDR and TRANSPORT ExtOrPort
// commands, respectively. If either is "", the corresponding command is not
// sent.
func DialOr(info *ServerInfo, addr, methodName string) (*net.TCPConn, error) {
if info.ExtendedOrAddr == nil || info.AuthCookiePath == "" {
return net.DialTCP("tcp", nil, info.OrAddr)
}
s, err := net.DialTCP("tcp", nil, info.ExtendedOrAddr)
if err != nil {
return nil, err
}
s.SetDeadline(time.Now().Add(5 * time.Second))
err = extOrPortAuthenticate(s, info)
if err != nil {
s.Close()
return nil, err
}
err = extOrPortSetup(s, addr, methodName)
if err != nil {
s.Close()
return nil, err
}
s.SetDeadline(time.Time{})
return s, nil
}

View File

@ -1,507 +0,0 @@
package pt
import (
"bufio"
"fmt"
"io"
"net"
"time"
)
const (
socksVersion = 0x05
socksAuthNoneRequired = 0x00
socksAuthUsernamePassword = 0x02
socksAuthNoAcceptableMethods = 0xff
socksCmdConnect = 0x01
socksRsv = 0x00
socksAtypeV4 = 0x01
socksAtypeDomainName = 0x03
socksAtypeV6 = 0x04
socksAuthRFC1929Ver = 0x01
socksAuthRFC1929Success = 0x00
socksAuthRFC1929Fail = 0x01
socksRepSucceeded = 0x00
// "general SOCKS server failure"
SocksRepGeneralFailure = 0x01
// "connection not allowed by ruleset"
SocksRepConnectionNotAllowed = 0x02
// "Network unreachable"
SocksRepNetworkUnreachable = 0x03
// "Host unreachable"
SocksRepHostUnreachable = 0x04
// "Connection refused"
SocksRepConnectionRefused = 0x05
// "TTL expired"
SocksRepTTLExpired = 0x06
// "Command not supported"
SocksRepCommandNotSupported = 0x07
// "Address type not supported"
SocksRepAddressNotSupported = 0x08
)
// Put a sanity timeout on how long we wait for a SOCKS request.
const socksRequestTimeout = 5 * time.Second
// SocksRequest describes a SOCKS request.
type SocksRequest struct {
// The endpoint requested by the client as a "host:port" string.
Target string
// The userid string sent by the client.
Username string
// The password string sent by the client.
Password string
// The parsed contents of Username as a keyvalue mapping.
Args Args
}
// SocksConn encapsulates a net.Conn and information associated with a SOCKS request.
type SocksConn struct {
net.Conn
Req SocksRequest
}
// Send a message to the proxy client that access to the given address is
// granted. Addr is ignored, and "0.0.0.0:0" is always sent back for
// BND.ADDR/BND.PORT in the SOCKS response.
func (conn *SocksConn) Grant(addr *net.TCPAddr) error {
return sendSocks5ResponseGranted(conn)
}
// Send a message to the proxy client that access was rejected or failed. This
// sends back a "General Failure" error code. RejectReason should be used if
// more specific error reporting is desired.
func (conn *SocksConn) Reject() error {
return conn.RejectReason(SocksRepGeneralFailure)
}
// Send a message to the proxy client that access was rejected, with the
// specific error code indicating the reason behind the rejection.
func (conn *SocksConn) RejectReason(reason byte) error {
return sendSocks5ResponseRejected(conn, reason)
}
// SocksListener wraps a net.Listener in order to read a SOCKS request on Accept.
//
// func handleConn(conn *pt.SocksConn) error {
// defer conn.Close()
// remote, err := net.Dial("tcp", conn.Req.Target)
// if err != nil {
// conn.Reject()
// return err
// }
// defer remote.Close()
// err = conn.Grant(remote.RemoteAddr().(*net.TCPAddr))
// if err != nil {
// return err
// }
// // do something with conn and remote
// return nil
// }
// ...
// ln, err := pt.ListenSocks("tcp", "127.0.0.1:0")
// if err != nil {
// panic(err.Error())
// }
// for {
// conn, err := ln.AcceptSocks()
// if err != nil {
// log.Printf("accept error: %s", err)
// if e, ok := err.(net.Error); ok && e.Temporary() {
// continue
// }
// break
// }
// go handleConn(conn)
// }
type SocksListener struct {
net.Listener
}
// Open a net.Listener according to network and laddr, and return it as a
// SocksListener.
func ListenSocks(network, laddr string) (*SocksListener, error) {
ln, err := net.Listen(network, laddr)
if err != nil {
return nil, err
}
return NewSocksListener(ln), nil
}
// Create a new SocksListener wrapping the given net.Listener.
func NewSocksListener(ln net.Listener) *SocksListener {
return &SocksListener{ln}
}
// Accept is the same as AcceptSocks, except that it returns a generic net.Conn.
// It is present for the sake of satisfying the net.Listener interface.
func (ln *SocksListener) Accept() (net.Conn, error) {
return ln.AcceptSocks()
}
// Call Accept on the wrapped net.Listener, do SOCKS negotiation, and return a
// SocksConn. After accepting, you must call either conn.Grant or conn.Reject
// (presumably after trying to connect to conn.Req.Target).
//
// Errors returned by AcceptSocks may be temporary (for example, EOF while
// reading the request, or a badly formatted userid string), or permanent (e.g.,
// the underlying socket is closed). You can determine whether an error is
// temporary and take appropriate action with a type conversion to net.Error.
// For example:
//
// for {
// conn, err := ln.AcceptSocks()
// if err != nil {
// if e, ok := err.(net.Error); ok && e.Temporary() {
// log.Printf("temporary accept error; trying again: %s", err)
// continue
// }
// log.Printf("permanent accept error; giving up: %s", err)
// break
// }
// go handleConn(conn)
// }
func (ln *SocksListener) AcceptSocks() (*SocksConn, error) {
retry:
c, err := ln.Listener.Accept()
if err != nil {
return nil, err
}
conn := new(SocksConn)
conn.Conn = c
err = conn.SetDeadline(time.Now().Add(socksRequestTimeout))
if err != nil {
conn.Close()
goto retry
}
conn.Req, err = socks5Handshake(conn)
if err != nil {
conn.Close()
goto retry
}
err = conn.SetDeadline(time.Time{})
if err != nil {
conn.Close()
goto retry
}
return conn, nil
}
// Returns "socks5", suitable to be included in a call to Cmethod.
func (ln *SocksListener) Version() string {
return "socks5"
}
// socks5handshake conducts the SOCKS5 handshake up to the point where the
// client command is read and the proxy must open the outgoing connection.
// Returns a SocksRequest.
func socks5Handshake(s io.ReadWriter) (req SocksRequest, err error) {
rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s))
// Negotiate the authentication method.
var method byte
if method, err = socksNegotiateAuth(rw); err != nil {
return
}
// Authenticate the client.
if err = socksAuthenticate(rw, method, &req); err != nil {
return
}
// Read the command.
err = socksReadCommand(rw, &req)
return
}
// socksNegotiateAuth negotiates the authentication method and returns the
// selected method as a byte. On negotiation failures an error is returned.
func socksNegotiateAuth(rw *bufio.ReadWriter) (method byte, err error) {
// Validate the version.
if err = socksReadByteVerify(rw, "version", socksVersion); err != nil {
return
}
// Read the number of methods.
var nmethods byte
if nmethods, err = socksReadByte(rw); err != nil {
return
}
// Read the methods.
var methods []byte
if methods, err = socksReadBytes(rw, int(nmethods)); err != nil {
return
}
// Pick the most "suitable" method.
method = socksAuthNoAcceptableMethods
for _, m := range methods {
switch m {
case socksAuthNoneRequired:
// Pick Username/Password over None if the client happens to
// send both.
if method == socksAuthNoAcceptableMethods {
method = m
}
case socksAuthUsernamePassword:
method = m
}
}
// Send the negotiated method.
var msg [2]byte
msg[0] = socksVersion
msg[1] = method
if _, err = rw.Writer.Write(msg[:]); err != nil {
return
}
if err = socksFlushBuffers(rw); err != nil {
return
}
return
}
// socksAuthenticate authenticates the client via the chosen authentication
// mechanism.
func socksAuthenticate(rw *bufio.ReadWriter, method byte, req *SocksRequest) (err error) {
switch method {
case socksAuthNoneRequired:
// Straight into reading the connect.
case socksAuthUsernamePassword:
if err = socksAuthRFC1929(rw, req); err != nil {
return
}
case socksAuthNoAcceptableMethods:
err = fmt.Errorf("SOCKS method select had no compatible methods")
return
default:
err = fmt.Errorf("SOCKS method select picked a unsupported method 0x%02x", method)
return
}
if err = socksFlushBuffers(rw); err != nil {
return
}
return
}
// socksAuthRFC1929 authenticates the client via RFC 1929 username/password
// auth. As a design decision any valid username/password is accepted as this
// field is primarily used as an out-of-band argument passing mechanism for
// pluggable transports.
func socksAuthRFC1929(rw *bufio.ReadWriter, req *SocksRequest) (err error) {
sendErrResp := func() {
// Swallow the write/flush error here, we are going to close the
// connection and the original failure is more useful.
resp := []byte{socksAuthRFC1929Ver, socksAuthRFC1929Fail}
rw.Write(resp[:])
socksFlushBuffers(rw)
}
// Validate the fixed parts of the command message.
if err = socksReadByteVerify(rw, "auth version", socksAuthRFC1929Ver); err != nil {
sendErrResp()
return
}
// Read the username.
var ulen byte
if ulen, err = socksReadByte(rw); err != nil {
return
}
if ulen < 1 {
sendErrResp()
err = fmt.Errorf("RFC1929 username with 0 length")
return
}
var uname []byte
if uname, err = socksReadBytes(rw, int(ulen)); err != nil {
return
}
req.Username = string(uname)
// Read the password.
var plen byte
if plen, err = socksReadByte(rw); err != nil {
return
}
if plen < 1 {
sendErrResp()
err = fmt.Errorf("RFC1929 password with 0 length")
return
}
var passwd []byte
if passwd, err = socksReadBytes(rw, int(plen)); err != nil {
return
}
if !(plen == 1 && passwd[0] == 0x00) {
// tor will set the password to 'NUL' if there are no arguments.
req.Password = string(passwd)
}
// Mash the username/password together and parse it as a pluggable
// transport argument string.
if req.Args, err = parseClientParameters(req.Username + req.Password); err != nil {
sendErrResp()
} else {
resp := []byte{socksAuthRFC1929Ver, socksAuthRFC1929Success}
_, err = rw.Write(resp[:])
}
return
}
// socksReadCommand reads a SOCKS5 client command and parses out the relevant
// fields into a SocksRequest. Only CMD_CONNECT is supported.
func socksReadCommand(rw *bufio.ReadWriter, req *SocksRequest) (err error) {
sendErrResp := func(reason byte) {
// Swallow errors that occur when writing/flushing the response,
// connection will be closed anyway.
sendSocks5ResponseRejected(rw, reason)
socksFlushBuffers(rw)
}
// Validate the fixed parts of the command message.
if err = socksReadByteVerify(rw, "version", socksVersion); err != nil {
sendErrResp(SocksRepGeneralFailure)
return
}
if err = socksReadByteVerify(rw, "command", socksCmdConnect); err != nil {
sendErrResp(SocksRepCommandNotSupported)
return
}
if err = socksReadByteVerify(rw, "reserved", socksRsv); err != nil {
sendErrResp(SocksRepGeneralFailure)
return
}
// Read the destination address/port.
// XXX: This should probably eventually send socks 5 error messages instead
// of rudely closing connections on invalid addresses.
var atype byte
if atype, err = socksReadByte(rw); err != nil {
return
}
var host string
switch atype {
case socksAtypeV4:
var addr []byte
if addr, err = socksReadBytes(rw, net.IPv4len); err != nil {
return
}
host = net.IPv4(addr[0], addr[1], addr[2], addr[3]).String()
case socksAtypeDomainName:
var alen byte
if alen, err = socksReadByte(rw); err != nil {
return
}
if alen == 0 {
err = fmt.Errorf("SOCKS request had domain name with 0 length")
return
}
var addr []byte
if addr, err = socksReadBytes(rw, int(alen)); err != nil {
return
}
host = string(addr)
case socksAtypeV6:
var rawAddr []byte
if rawAddr, err = socksReadBytes(rw, net.IPv6len); err != nil {
return
}
addr := make(net.IP, net.IPv6len)
copy(addr[:], rawAddr[:])
host = fmt.Sprintf("[%s]", addr.String())
default:
sendErrResp(SocksRepAddressNotSupported)
err = fmt.Errorf("SOCKS request had unsupported address type 0x%02x", atype)
return
}
var rawPort []byte
if rawPort, err = socksReadBytes(rw, 2); err != nil {
return
}
port := int(rawPort[0])<<8 | int(rawPort[1])<<0
if err = socksFlushBuffers(rw); err != nil {
return
}
req.Target = fmt.Sprintf("%s:%d", host, port)
return
}
// Send a SOCKS5 response with the given code. BND.ADDR/BND.PORT is always the
// IPv4 address/port "0.0.0.0:0".
func sendSocks5Response(w io.Writer, code byte) error {
resp := make([]byte, 4+4+2)
resp[0] = socksVersion
resp[1] = code
resp[2] = socksRsv
resp[3] = socksAtypeV4
// BND.ADDR/BND.PORT should be the address and port that the outgoing
// connection is bound to on the proxy, but Tor does not use this
// information, so all zeroes are sent.
_, err := w.Write(resp[:])
return err
}
// Send a SOCKS5 response code 0x00.
func sendSocks5ResponseGranted(w io.Writer) error {
return sendSocks5Response(w, socksRepSucceeded)
}
// Send a SOCKS5 response with the provided failure reason.
func sendSocks5ResponseRejected(w io.Writer, reason byte) error {
return sendSocks5Response(w, reason)
}
func socksFlushBuffers(rw *bufio.ReadWriter) error {
if err := rw.Writer.Flush(); err != nil {
return err
}
if rw.Reader.Buffered() > 0 {
return fmt.Errorf("%d bytes left after SOCKS message", rw.Reader.Buffered())
}
return nil
}
func socksReadByte(rw *bufio.ReadWriter) (byte, error) {
return rw.Reader.ReadByte()
}
func socksReadBytes(rw *bufio.ReadWriter, n int) ([]byte, error) {
ret := make([]byte, n)
if _, err := io.ReadFull(rw.Reader, ret); err != nil {
return nil, err
}
return ret, nil
}
func socksReadByteVerify(rw *bufio.ReadWriter, descr string, expected byte) error {
val, err := socksReadByte(rw)
if err != nil {
return err
}
if val != expected {
return fmt.Errorf("SOCKS message field %s was 0x%02x, not 0x%02x", descr, val, expected)
}
return nil
}
var _ net.Listener = (*SocksListener)(nil)

View File

@ -1,55 +0,0 @@
Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
==============================================================================
Copyright (c) 2012 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,101 +0,0 @@
/*
* Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
// Package csrand implements the math/rand interface over crypto/rand, along
// with some utility functions for common random number/byte related tasks.
//
// Not all of the convinience routines are replicated, only those that are
// immediately useful. The Rand variable provides access to the full math/rand
// API.
package csrand
import (
cryptRand "crypto/rand"
"encoding/binary"
"fmt"
"io"
"math/rand"
)
var (
csRandSourceInstance csRandSource
// Rand is a math/rand instance backed by crypto/rand CSPRNG.
Rand = rand.New(csRandSourceInstance)
)
type csRandSource struct {
// This does not keep any state as it is backed by crypto/rand.
}
func (r csRandSource) Int63() int64 {
var src [8]byte
if err := Bytes(src[:]); err != nil {
panic(err)
}
val := binary.BigEndian.Uint64(src[:])
val &= (1<<63 - 1)
return int64(val)
}
func (r csRandSource) Seed(seed int64) {
// No-op.
}
// Intn returns, as a int, a pseudo random number in [0, n).
func Intn(n int) int {
return Rand.Intn(n)
}
// Float64 returns, as a float64, a pesudo random number in [0.0,1.0).
func Float64() float64 {
return Rand.Float64()
}
// IntRange returns a uniformly distributed int [min, max].
func IntRange(min, max int) int {
if max < min {
panic(fmt.Sprintf("IntRange: min > max (%d, %d)", min, max))
}
r := (max + 1) - min
ret := Rand.Intn(r)
return ret + min
}
// Bytes fills the slice with random data.
func Bytes(buf []byte) error {
if _, err := io.ReadFull(cryptRand.Reader, buf); err != nil {
return err
}
return nil
}
// Reader is a alias of rand.Reader.
var Reader = cryptRand.Reader

View File

@ -1,149 +0,0 @@
/*
* Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
// Package drbg implements a minimalistic DRBG based off SipHash-2-4 in OFB
// mode.
package drbg
import (
"encoding/binary"
"encoding/hex"
"fmt"
"hash"
"github.com/dchest/siphash"
"git.torproject.org/pluggable-transports/obfs4.git/common/csrand"
)
// Size is the length of the HashDrbg output.
const Size = siphash.Size
// SeedLength is the length of the HashDrbg seed.
const SeedLength = 16 + Size
// Seed is the initial state for a HashDrbg. It consists of a SipHash-2-4
// key, and 8 bytes of initial data.
type Seed [SeedLength]byte
// Bytes returns a pointer to the raw HashDrbg seed.
func (seed *Seed) Bytes() *[SeedLength]byte {
return (*[SeedLength]byte)(seed)
}
// Hex returns the hexdecimal representation of the seed.
func (seed *Seed) Hex() string {
return hex.EncodeToString(seed.Bytes()[:])
}
// NewSeed returns a Seed initialized with the runtime CSPRNG.
func NewSeed() (seed *Seed, err error) {
seed = new(Seed)
if err = csrand.Bytes(seed.Bytes()[:]); err != nil {
return nil, err
}
return
}
// SeedFromBytes creates a Seed from the raw bytes, truncating to SeedLength as
// appropriate.
func SeedFromBytes(src []byte) (seed *Seed, err error) {
if len(src) < SeedLength {
return nil, InvalidSeedLengthError(len(src))
}
seed = new(Seed)
copy(seed.Bytes()[:], src)
return
}
// SeedFromHex creates a Seed from the hexdecimal representation, truncating to
// SeedLength as appropriate.
func SeedFromHex(encoded string) (seed *Seed, err error) {
var raw []byte
if raw, err = hex.DecodeString(encoded); err != nil {
return nil, err
}
return SeedFromBytes(raw)
}
// InvalidSeedLengthError is the error returned when the seed provided to the
// DRBG is an invalid length.
type InvalidSeedLengthError int
func (e InvalidSeedLengthError) Error() string {
return fmt.Sprintf("invalid seed length: %d", int(e))
}
// HashDrbg is a CSDRBG based off of SipHash-2-4 in OFB mode.
type HashDrbg struct {
sip hash.Hash64
ofb [Size]byte
}
// NewHashDrbg makes a HashDrbg instance based off an optional seed. The seed
// is truncated to SeedLength.
func NewHashDrbg(seed *Seed) (*HashDrbg, error) {
drbg := new(HashDrbg)
if seed == nil {
var err error
if seed, err = NewSeed(); err != nil {
return nil, err
}
}
drbg.sip = siphash.New(seed.Bytes()[:16])
copy(drbg.ofb[:], seed.Bytes()[16:])
return drbg, nil
}
// Int63 returns a uniformly distributed random integer [0, 1 << 63).
func (drbg *HashDrbg) Int63() int64 {
block := drbg.NextBlock()
ret := binary.BigEndian.Uint64(block)
ret &= (1<<63 - 1)
return int64(ret)
}
// Seed does nothing, call NewHashDrbg if you want to reseed.
func (drbg *HashDrbg) Seed(seed int64) {
// No-op.
}
// NextBlock returns the next 8 byte DRBG block.
func (drbg *HashDrbg) NextBlock() []byte {
drbg.sip.Write(drbg.ofb[:])
copy(drbg.ofb[:], drbg.sip.Sum(nil))
ret := make([]byte, Size)
copy(ret, drbg.ofb[:])
return ret
}

View File

@ -1,433 +0,0 @@
/*
* Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
// Package ntor implements the Tor Project's ntor handshake as defined in
// proposal 216 "Improved circuit-creation key exchange". It also supports
// using Elligator to transform the Curve25519 public keys sent over the wire
// to a form that is indistinguishable from random strings.
//
// Before using this package, it is strongly recommended that the specification
// is read and understood.
package ntor
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
"fmt"
"io"
"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/hkdf"
"github.com/agl/ed25519/extra25519"
"git.torproject.org/pluggable-transports/obfs4.git/common/csrand"
)
const (
// PublicKeyLength is the length of a Curve25519 public key.
PublicKeyLength = 32
// RepresentativeLength is the length of an Elligator representative.
RepresentativeLength = 32
// PrivateKeyLength is the length of a Curve25519 private key.
PrivateKeyLength = 32
// SharedSecretLength is the length of a Curve25519 shared secret.
SharedSecretLength = 32
// NodeIDLength is the length of a ntor node identifier.
NodeIDLength = 20
// KeySeedLength is the length of the derived KEY_SEED.
KeySeedLength = sha256.Size
// AuthLength is the lenght of the derived AUTH.
AuthLength = sha256.Size
)
var protoID = []byte("ntor-curve25519-sha256-1")
var tMac = append(protoID, []byte(":mac")...)
var tKey = append(protoID, []byte(":key_extract")...)
var tVerify = append(protoID, []byte(":key_verify")...)
var mExpand = append(protoID, []byte(":key_expand")...)
// PublicKeyLengthError is the error returned when the public key being
// imported is an invalid length.
type PublicKeyLengthError int
func (e PublicKeyLengthError) Error() string {
return fmt.Sprintf("ntor: Invalid Curve25519 public key length: %d",
int(e))
}
// PrivateKeyLengthError is the error returned when the private key being
// imported is an invalid length.
type PrivateKeyLengthError int
func (e PrivateKeyLengthError) Error() string {
return fmt.Sprintf("ntor: Invalid Curve25519 private key length: %d",
int(e))
}
// NodeIDLengthError is the error returned when the node ID being imported is
// an invalid length.
type NodeIDLengthError int
func (e NodeIDLengthError) Error() string {
return fmt.Sprintf("ntor: Invalid NodeID length: %d", int(e))
}
// KeySeed is the key material that results from a handshake (KEY_SEED).
type KeySeed [KeySeedLength]byte
// Bytes returns a pointer to the raw key material.
func (key_seed *KeySeed) Bytes() *[KeySeedLength]byte {
return (*[KeySeedLength]byte)(key_seed)
}
// Auth is the verifier that results from a handshake (AUTH).
type Auth [AuthLength]byte
// Bytes returns a pointer to the raw auth.
func (auth *Auth) Bytes() *[AuthLength]byte {
return (*[AuthLength]byte)(auth)
}
// NodeID is a ntor node identifier.
type NodeID [NodeIDLength]byte
// NewNodeID creates a NodeID from the raw bytes.
func NewNodeID(raw []byte) (*NodeID, error) {
if len(raw) != NodeIDLength {
return nil, NodeIDLengthError(len(raw))
}
nodeID := new(NodeID)
copy(nodeID[:], raw)
return nodeID, nil
}
// NodeIDFromHex creates a new NodeID from the hexdecimal representation.
func NodeIDFromHex(encoded string) (*NodeID, error) {
raw, err := hex.DecodeString(encoded)
if err != nil {
return nil, err
}
return NewNodeID(raw)
}
// Bytes returns a pointer to the raw NodeID.
func (id *NodeID) Bytes() *[NodeIDLength]byte {
return (*[NodeIDLength]byte)(id)
}
// Hex returns the hexdecimal representation of the NodeID.
func (id *NodeID) Hex() string {
return hex.EncodeToString(id[:])
}
// PublicKey is a Curve25519 public key in little-endian byte order.
type PublicKey [PublicKeyLength]byte
// Bytes returns a pointer to the raw Curve25519 public key.
func (public *PublicKey) Bytes() *[PublicKeyLength]byte {
return (*[PublicKeyLength]byte)(public)
}
// Hex returns the hexdecimal representation of the Curve25519 public key.
func (public *PublicKey) Hex() string {
return hex.EncodeToString(public.Bytes()[:])
}
// NewPublicKey creates a PublicKey from the raw bytes.
func NewPublicKey(raw []byte) (*PublicKey, error) {
if len(raw) != PublicKeyLength {
return nil, PublicKeyLengthError(len(raw))
}
pubKey := new(PublicKey)
copy(pubKey[:], raw)
return pubKey, nil
}
// PublicKeyFromHex returns a PublicKey from the hexdecimal representation.
func PublicKeyFromHex(encoded string) (*PublicKey, error) {
raw, err := hex.DecodeString(encoded)
if err != nil {
return nil, err
}
return NewPublicKey(raw)
}
// Representative is an Elligator representative of a Curve25519 public key
// in little-endian byte order.
type Representative [RepresentativeLength]byte
// Bytes returns a pointer to the raw Elligator representative.
func (repr *Representative) Bytes() *[RepresentativeLength]byte {
return (*[RepresentativeLength]byte)(repr)
}
// ToPublic converts a Elligator representative to a Curve25519 public key.
func (repr *Representative) ToPublic() *PublicKey {
pub := new(PublicKey)
extra25519.RepresentativeToPublicKey(pub.Bytes(), repr.Bytes())
return pub
}
// PrivateKey is a Curve25519 private key in little-endian byte order.
type PrivateKey [PrivateKeyLength]byte
// Bytes returns a pointer to the raw Curve25519 private key.
func (private *PrivateKey) Bytes() *[PrivateKeyLength]byte {
return (*[PrivateKeyLength]byte)(private)
}
// Hex returns the hexdecimal representation of the Curve25519 private key.
func (private *PrivateKey) Hex() string {
return hex.EncodeToString(private.Bytes()[:])
}
// Keypair is a Curve25519 keypair with an optional Elligator representative.
// As only certain Curve25519 keys can be obfuscated with Elligator, the
// representative must be generated along with the keypair.
type Keypair struct {
public *PublicKey
private *PrivateKey
representative *Representative
}
// Public returns the Curve25519 public key belonging to the Keypair.
func (keypair *Keypair) Public() *PublicKey {
return keypair.public
}
// Private returns the Curve25519 private key belonging to the Keypair.
func (keypair *Keypair) Private() *PrivateKey {
return keypair.private
}
// Representative returns the Elligator representative of the public key
// belonging to the Keypair.
func (keypair *Keypair) Representative() *Representative {
return keypair.representative
}
// HasElligator returns true if the Keypair has an Elligator representative.
func (keypair *Keypair) HasElligator() bool {
return nil != keypair.representative
}
// NewKeypair generates a new Curve25519 keypair, and optionally also generates
// an Elligator representative of the public key.
func NewKeypair(elligator bool) (*Keypair, error) {
keypair := new(Keypair)
keypair.private = new(PrivateKey)
keypair.public = new(PublicKey)
if elligator {
keypair.representative = new(Representative)
}
for {
// Generate a Curve25519 private key. Like everyone who does this,
// run the CSPRNG output through SHA256 for extra tinfoil hattery.
priv := keypair.private.Bytes()[:]
if err := csrand.Bytes(priv); err != nil {
return nil, err
}
digest := sha256.Sum256(priv)
digest[0] &= 248
digest[31] &= 127
digest[31] |= 64
copy(priv, digest[:])
if elligator {
// Apply the Elligator transform. This fails ~50% of the time.
if !extra25519.ScalarBaseMult(keypair.public.Bytes(),
keypair.representative.Bytes(),
keypair.private.Bytes()) {
continue
}
} else {
// Generate the corresponding Curve25519 public key.
curve25519.ScalarBaseMult(keypair.public.Bytes(),
keypair.private.Bytes())
}
return keypair, nil
}
}
// KeypairFromHex returns a Keypair from the hexdecimal representation of the
// private key.
func KeypairFromHex(encoded string) (*Keypair, error) {
raw, err := hex.DecodeString(encoded)
if err != nil {
return nil, err
}
if len(raw) != PrivateKeyLength {
return nil, PrivateKeyLengthError(len(raw))
}
keypair := new(Keypair)
keypair.private = new(PrivateKey)
keypair.public = new(PublicKey)
copy(keypair.private[:], raw)
curve25519.ScalarBaseMult(keypair.public.Bytes(),
keypair.private.Bytes())
return keypair, nil
}
// ServerHandshake does the server side of a ntor handshake and returns status,
// KEY_SEED, and AUTH. If status is not true, the handshake MUST be aborted.
func ServerHandshake(clientPublic *PublicKey, serverKeypair *Keypair, idKeypair *Keypair, id *NodeID) (ok bool, keySeed *KeySeed, auth *Auth) {
var notOk int
var secretInput bytes.Buffer
// Server side uses EXP(X,y) | EXP(X,b)
var exp [SharedSecretLength]byte
curve25519.ScalarMult(&exp, serverKeypair.private.Bytes(),
clientPublic.Bytes())
notOk |= constantTimeIsZero(exp[:])
secretInput.Write(exp[:])
curve25519.ScalarMult(&exp, idKeypair.private.Bytes(),
clientPublic.Bytes())
notOk |= constantTimeIsZero(exp[:])
secretInput.Write(exp[:])
keySeed, auth = ntorCommon(secretInput, id, idKeypair.public,
clientPublic, serverKeypair.public)
return notOk == 0, keySeed, auth
}
// ClientHandshake does the client side of a ntor handshake and returnes
// status, KEY_SEED, and AUTH. If status is not true or AUTH does not match
// the value recieved from the server, the handshake MUST be aborted.
func ClientHandshake(clientKeypair *Keypair, serverPublic *PublicKey, idPublic *PublicKey, id *NodeID) (ok bool, keySeed *KeySeed, auth *Auth) {
var notOk int
var secretInput bytes.Buffer
// Client side uses EXP(Y,x) | EXP(B,x)
var exp [SharedSecretLength]byte
curve25519.ScalarMult(&exp, clientKeypair.private.Bytes(),
serverPublic.Bytes())
notOk |= constantTimeIsZero(exp[:])
secretInput.Write(exp[:])
curve25519.ScalarMult(&exp, clientKeypair.private.Bytes(),
idPublic.Bytes())
notOk |= constantTimeIsZero(exp[:])
secretInput.Write(exp[:])
keySeed, auth = ntorCommon(secretInput, id, idPublic,
clientKeypair.public, serverPublic)
return notOk == 0, keySeed, auth
}
// CompareAuth does a constant time compare of a Auth and a byte slice
// (presumably received over a network).
func CompareAuth(auth1 *Auth, auth2 []byte) bool {
auth1Bytes := auth1.Bytes()
return hmac.Equal(auth1Bytes[:], auth2)
}
func ntorCommon(secretInput bytes.Buffer, id *NodeID, b *PublicKey, x *PublicKey, y *PublicKey) (*KeySeed, *Auth) {
keySeed := new(KeySeed)
auth := new(Auth)
// secret_input/auth_input use this common bit, build it once.
suffix := bytes.NewBuffer(b.Bytes()[:])
suffix.Write(b.Bytes()[:])
suffix.Write(x.Bytes()[:])
suffix.Write(y.Bytes()[:])
suffix.Write(protoID)
suffix.Write(id[:])
// At this point secret_input has the 2 exponents, concatenated, append the
// client/server common suffix.
secretInput.Write(suffix.Bytes())
// KEY_SEED = H(secret_input, t_key)
h := hmac.New(sha256.New, tKey)
h.Write(secretInput.Bytes())
tmp := h.Sum(nil)
copy(keySeed[:], tmp)
// verify = H(secret_input, t_verify)
h = hmac.New(sha256.New, tVerify)
h.Write(secretInput.Bytes())
verify := h.Sum(nil)
// auth_input = verify | ID | B | Y | X | PROTOID | "Server"
authInput := bytes.NewBuffer(verify)
authInput.Write(suffix.Bytes())
authInput.Write([]byte("Server"))
h = hmac.New(sha256.New, tMac)
h.Write(authInput.Bytes())
tmp = h.Sum(nil)
copy(auth[:], tmp)
return keySeed, auth
}
func constantTimeIsZero(x []byte) int {
var ret byte
for _, v := range x {
ret |= v
}
return subtle.ConstantTimeByteEq(ret, 0)
}
// Kdf extracts and expands KEY_SEED via HKDF-SHA256 and returns `okm_len` bytes
// of key material.
func Kdf(keySeed []byte, okmLen int) []byte {
kdf := hkdf.New(sha256.New, keySeed, tKey, mExpand)
okm := make([]byte, okmLen)
n, err := io.ReadFull(kdf, okm)
if err != nil {
panic(fmt.Sprintf("BUG: Failed HKDF: %s", err.Error()))
} else if n != len(okm) {
panic(fmt.Sprintf("BUG: Got truncated HKDF output: %d", n))
}
return okm
}

View File

@ -1,245 +0,0 @@
/*
* Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
// Package probdist implements a weighted probability distribution suitable for
// protocol parameterization. To allow for easy reproduction of a given
// distribution, the drbg package is used as the random number source.
package probdist
import (
"bytes"
"container/list"
"fmt"
"math/rand"
"sync"
"git.torproject.org/pluggable-transports/obfs4.git/common/csrand"
"git.torproject.org/pluggable-transports/obfs4.git/common/drbg"
)
const (
minValues = 1
maxValues = 100
)
// WeightedDist is a weighted distribution.
type WeightedDist struct {
sync.Mutex
minValue int
maxValue int
biased bool
values []int
weights []float64
alias []int
prob []float64
}
// New creates a weighted distribution of values ranging from min to max
// based on a HashDrbg initialized with seed. Optionally, bias the weight
// generation to match the ScrambleSuit non-uniform distribution from
// obfsproxy.
func New(seed *drbg.Seed, min, max int, biased bool) (w *WeightedDist) {
w = &WeightedDist{minValue: min, maxValue: max, biased: biased}
if max <= min {
panic(fmt.Sprintf("wDist.Reset(): min >= max (%d, %d)", min, max))
}
w.Reset(seed)
return
}
// genValues creates a slice containing a random number of random values
// that when scaled by adding minValue will fall into [min, max].
func (w *WeightedDist) genValues(rng *rand.Rand) {
nValues := (w.maxValue + 1) - w.minValue
values := rng.Perm(nValues)
if nValues < minValues {
nValues = minValues
}
if nValues > maxValues {
nValues = maxValues
}
nValues = rng.Intn(nValues) + 1
w.values = values[:nValues]
}
// genBiasedWeights generates a non-uniform weight list, similar to the
// ScrambleSuit prob_dist module.
func (w *WeightedDist) genBiasedWeights(rng *rand.Rand) {
w.weights = make([]float64, len(w.values))
culmProb := 0.0
for i := range w.weights {
p := (1.0 - culmProb) * rng.Float64()
w.weights[i] = p
culmProb += p
}
}
// genUniformWeights generates a uniform weight list.
func (w *WeightedDist) genUniformWeights(rng *rand.Rand) {
w.weights = make([]float64, len(w.values))
for i := range w.weights {
w.weights[i] = rng.Float64()
}
}
// genTables calculates the alias and prob tables used for Vose's Alias method.
// Algorithm taken from http://www.keithschwarz.com/darts-dice-coins/
func (w *WeightedDist) genTables() {
n := len(w.weights)
var sum float64
for _, weight := range w.weights {
sum += weight
}
// Create arrays $Alias$ and $Prob$, each of size $n$.
alias := make([]int, n)
prob := make([]float64, n)
// Create two worklists, $Small$ and $Large$.
small := list.New()
large := list.New()
scaled := make([]float64, n)
for i, weight := range w.weights {
// Multiply each probability by $n$.
p_i := weight * float64(n) / sum
scaled[i] = p_i
// For each scaled probability $p_i$:
if scaled[i] < 1.0 {
// If $p_i < 1$, add $i$ to $Small$.
small.PushBack(i)
} else {
// Otherwise ($p_i \ge 1$), add $i$ to $Large$.
large.PushBack(i)
}
}
// While $Small$ and $Large$ are not empty: ($Large$ might be emptied first)
for small.Len() > 0 && large.Len() > 0 {
// Remove the first element from $Small$; call it $l$.
l := small.Remove(small.Front()).(int)
// Remove the first element from $Large$; call it $g$.
g := large.Remove(large.Front()).(int)
// Set $Prob[l] = p_l$.
prob[l] = scaled[l]
// Set $Alias[l] = g$.
alias[l] = g
// Set $p_g := (p_g + p_l) - 1$. (This is a more numerically stable option.)
scaled[g] = (scaled[g] + scaled[l]) - 1.0
if scaled[g] < 1.0 {
// If $p_g < 1$, add $g$ to $Small$.
small.PushBack(g)
} else {
// Otherwise ($p_g \ge 1$), add $g$ to $Large$.
large.PushBack(g)
}
}
// While $Large$ is not empty:
for large.Len() > 0 {
// Remove the first element from $Large$; call it $g$.
g := large.Remove(large.Front()).(int)
// Set $Prob[g] = 1$.
prob[g] = 1.0
}
// While $Small$ is not empty: This is only possible due to numerical instability.
for small.Len() > 0 {
// Remove the first element from $Small$; call it $l$.
l := small.Remove(small.Front()).(int)
// Set $Prob[l] = 1$.
prob[l] = 1.0
}
w.prob = prob
w.alias = alias
}
// Reset generates a new distribution with the same min/max based on a new
// seed.
func (w *WeightedDist) Reset(seed *drbg.Seed) {
// Initialize the deterministic random number generator.
drbg, _ := drbg.NewHashDrbg(seed)
rng := rand.New(drbg)
w.Lock()
defer w.Unlock()
w.genValues(rng)
if w.biased {
w.genBiasedWeights(rng)
} else {
w.genUniformWeights(rng)
}
w.genTables()
}
// Sample generates a random value according to the distribution.
func (w *WeightedDist) Sample() int {
var idx int
w.Lock()
defer w.Unlock()
// Generate a fair die roll from an $n$-sided die; call the side $i$.
i := csrand.Intn(len(w.values))
// Flip a biased coin that comes up heads with probability $Prob[i]$.
if csrand.Float64() <= w.prob[i] {
// If the coin comes up "heads," return $i$.
idx = i
} else {
// Otherwise, return $Alias[i]$.
idx = w.alias[i]
}
return w.minValue + w.values[idx]
}
// String returns a dump of the distribution table.
func (w *WeightedDist) String() string {
var buf bytes.Buffer
buf.WriteString("[ ")
for i, v := range w.values {
p := w.weights[i]
if p > 0.01 { // Squelch tiny probabilities.
buf.WriteString(fmt.Sprintf("%d: %f ", v, p))
}
}
buf.WriteString("]")
return buf.String()
}

View File

@ -1,147 +0,0 @@
/*
* Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
// Package replayfilter implements a generic replay detection filter with a
// caller specifiable time-to-live. It only detects if a given byte sequence
// has been seen before based on the SipHash-2-4 digest of the sequence.
// Collisions are treated as positive matches, though the probability of this
// happening is negligible.
package replayfilter
import (
"container/list"
"encoding/binary"
"sync"
"time"
"github.com/dchest/siphash"
"git.torproject.org/pluggable-transports/obfs4.git/common/csrand"
)
// maxFilterSize is the maximum capacity of a replay filter. This value is
// more as a safeguard to prevent runaway filter growth, and is sized to be
// serveral orders of magnitude greater than the number of connections a busy
// bridge sees in one day, so in practice should never be reached.
const maxFilterSize = 100 * 1024
type entry struct {
digest uint64
firstSeen time.Time
element *list.Element
}
// ReplayFilter is a simple filter designed only to detect if a given byte
// sequence has been seen before.
type ReplayFilter struct {
sync.Mutex
filter map[uint64]*entry
fifo *list.List
key [2]uint64
ttl time.Duration
}
// New creates a new ReplayFilter instance.
func New(ttl time.Duration) (filter *ReplayFilter, err error) {
// Initialize the SipHash-2-4 instance with a random key.
var key [16]byte
if err = csrand.Bytes(key[:]); err != nil {
return
}
filter = new(ReplayFilter)
filter.filter = make(map[uint64]*entry)
filter.fifo = list.New()
filter.key[0] = binary.BigEndian.Uint64(key[0:8])
filter.key[1] = binary.BigEndian.Uint64(key[8:16])
filter.ttl = ttl
return
}
// TestAndSet queries the filter for a given byte sequence, inserts the
// sequence, and returns if it was present before the insertion operation.
func (f *ReplayFilter) TestAndSet(now time.Time, buf []byte) bool {
digest := siphash.Hash(f.key[0], f.key[1], buf)
f.Lock()
defer f.Unlock()
f.compactFilter(now)
if e := f.filter[digest]; e != nil {
// Hit. Just return.
return true
}
// Miss. Add a new entry.
e := new(entry)
e.digest = digest
e.firstSeen = now
e.element = f.fifo.PushBack(e)
f.filter[digest] = e
return false
}
func (f *ReplayFilter) compactFilter(now time.Time) {
e := f.fifo.Front()
for e != nil {
ent, _ := e.Value.(*entry)
// If the filter is not full, only purge entries that exceed the TTL,
// otherwise purge at least one entry, then revert to TTL based
// compaction.
if f.fifo.Len() < maxFilterSize && f.ttl > 0 {
deltaT := now.Sub(ent.firstSeen)
if deltaT < 0 {
// Aeeeeeee, the system time jumped backwards, potentially by
// a lot. This will eventually self-correct, but "eventually"
// could be a long time. As much as this sucks, jettison the
// entire filter.
f.reset()
return
} else if deltaT < f.ttl {
return
}
}
// Remove the eldest entry.
eNext := e.Next()
delete(f.filter, ent.digest)
f.fifo.Remove(ent.element)
ent.element = nil
e = eNext
}
}
func (f *ReplayFilter) reset() {
f.filter = make(map[uint64]*entry)
f.fifo = list.New()
}

View File

@ -1,90 +0,0 @@
/*
* Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
// Package base provides the common interface that each supported transport
// protocol must implement.
package base
import (
"net"
"git.torproject.org/pluggable-transports/goptlib.git"
)
type DialFunc func(string, string) (net.Conn, error)
// ClientFactory is the interface that defines the factory for creating
// pluggable transport protocol client instances.
type ClientFactory interface {
// Transport returns the Transport instance that this ClientFactory belongs
// to.
Transport() Transport
// ParseArgs parses the supplied arguments into an internal representation
// for use with WrapConn. This routine is called before the outgoing
// TCP/IP connection is created to allow doing things (like keypair
// generation) to be hidden from third parties.
ParseArgs(args *pt.Args) (interface{}, error)
// Dial creates an outbound net.Conn, and does whatever is required
// (eg: handshaking) to get the connection to the point where it is
// ready to relay data.
Dial(network, address string, dialFn DialFunc, args interface{}) (net.Conn, error)
}
// ServerFactory is the interface that defines the factory for creating
// plugable transport protocol server instances. As the arguments are the
// property of the factory, validation is done at factory creation time.
type ServerFactory interface {
// Transport returns the Transport instance that this ServerFactory belongs
// to.
Transport() Transport
// Args returns the Args required on the client side to handshake with
// server connections created by this factory.
Args() *pt.Args
// WrapConn wraps the provided net.Conn with a transport protocol
// implementation, and does whatever is required (eg: handshaking) to get
// the connection to a point where it is ready to relay data.
WrapConn(conn net.Conn) (net.Conn, error)
}
// Transport is an interface that defines a pluggable transport protocol.
type Transport interface {
// Name returns the name of the transport protocol. It MUST be a valid C
// identifier.
Name() string
// ClientFactory returns a ClientFactory instance for this transport
// protocol.
ClientFactory(stateDir string) (ClientFactory, error)
// ServerFactory returns a ServerFactory instance for this transport
// protocol. This can fail if the provided arguments are invalid.
ServerFactory(stateDir string, args *pt.Args) (ServerFactory, error)
}

View File

@ -1,306 +0,0 @@
/*
* Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
//
// Package framing implements the obfs4 link framing and cryptography.
//
// The Encoder/Decoder shared secret format is:
// uint8_t[32] NaCl secretbox key
// uint8_t[16] NaCl Nonce prefix
// uint8_t[16] SipHash-2-4 key (used to obfsucate length)
// uint8_t[8] SipHash-2-4 IV
//
// The frame format is:
// uint16_t length (obfsucated, big endian)
// NaCl secretbox (Poly1305/XSalsa20) containing:
// uint8_t[16] tag (Part of the secretbox construct)
// uint8_t[] payload
//
// The length field is length of the NaCl secretbox XORed with the truncated
// SipHash-2-4 digest ran in OFB mode.
//
// Initialize K, IV[0] with values from the shared secret.
// On each packet, IV[n] = H(K, IV[n - 1])
// mask[n] = IV[n][0:2]
// obfsLen = length ^ mask[n]
//
// The NaCl secretbox (Poly1305/XSalsa20) nonce format is:
// uint8_t[24] prefix (Fixed)
// uint64_t counter (Big endian)
//
// The counter is initialized to 1, and is incremented on each frame. Since
// the protocol is designed to be used over a reliable medium, the nonce is not
// transmitted over the wire as both sides of the conversation know the prefix
// and the initial counter value. It is imperative that the counter does not
// wrap, and sessions MUST terminate before 2^64 frames are sent.
//
package framing
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"golang.org/x/crypto/nacl/secretbox"
"git.torproject.org/pluggable-transports/obfs4.git/common/csrand"
"git.torproject.org/pluggable-transports/obfs4.git/common/drbg"
)
const (
// MaximumSegmentLength is the length of the largest possible segment
// including overhead.
MaximumSegmentLength = 1500 - (40 + 12)
// FrameOverhead is the length of the framing overhead.
FrameOverhead = lengthLength + secretbox.Overhead
// MaximumFramePayloadLength is the length of the maximum allowed payload
// per frame.
MaximumFramePayloadLength = MaximumSegmentLength - FrameOverhead
// KeyLength is the length of the Encoder/Decoder secret key.
KeyLength = keyLength + noncePrefixLength + drbg.SeedLength
maxFrameLength = MaximumSegmentLength - lengthLength
minFrameLength = FrameOverhead - lengthLength
keyLength = 32
noncePrefixLength = 16
nonceCounterLength = 8
nonceLength = noncePrefixLength + nonceCounterLength
lengthLength = 2
)
// Error returned when Decoder.Decode() requires more data to continue.
var ErrAgain = errors.New("framing: More data needed to decode")
// Error returned when Decoder.Decode() failes to authenticate a frame.
var ErrTagMismatch = errors.New("framing: Poly1305 tag mismatch")
// Error returned when the NaCl secretbox nonce's counter wraps (FATAL).
var ErrNonceCounterWrapped = errors.New("framing: Nonce counter wrapped")
// InvalidPayloadLengthError is the error returned when Encoder.Encode()
// rejects the payload length.
type InvalidPayloadLengthError int
func (e InvalidPayloadLengthError) Error() string {
return fmt.Sprintf("framing: Invalid payload length: %d", int(e))
}
type boxNonce struct {
prefix [noncePrefixLength]byte
counter uint64
}
func (nonce *boxNonce) init(prefix []byte) {
if noncePrefixLength != len(prefix) {
panic(fmt.Sprintf("BUG: Nonce prefix length invalid: %d", len(prefix)))
}
copy(nonce.prefix[:], prefix)
nonce.counter = 1
}
func (nonce boxNonce) bytes(out *[nonceLength]byte) error {
// The security guarantee of Poly1305 is broken if a nonce is ever reused
// for a given key. Detect this by checking for counter wraparound since
// we start each counter at 1. If it ever happens that more than 2^64 - 1
// frames are transmitted over a given connection, support for rekeying
// will be neccecary, but that's unlikely to happen.
if nonce.counter == 0 {
return ErrNonceCounterWrapped
}
copy(out[:], nonce.prefix[:])
binary.BigEndian.PutUint64(out[noncePrefixLength:], nonce.counter)
return nil
}
// Encoder is a frame encoder instance.
type Encoder struct {
key [keyLength]byte
nonce boxNonce
drbg *drbg.HashDrbg
}
// NewEncoder creates a new Encoder instance. It must be supplied a slice
// containing exactly KeyLength bytes of keying material.
func NewEncoder(key []byte) *Encoder {
if len(key) != KeyLength {
panic(fmt.Sprintf("BUG: Invalid encoder key length: %d", len(key)))
}
encoder := new(Encoder)
copy(encoder.key[:], key[0:keyLength])
encoder.nonce.init(key[keyLength : keyLength+noncePrefixLength])
seed, err := drbg.SeedFromBytes(key[keyLength+noncePrefixLength:])
if err != nil {
panic(fmt.Sprintf("BUG: Failed to initialize DRBG: %s", err))
}
encoder.drbg, _ = drbg.NewHashDrbg(seed)
return encoder
}
// Encode encodes a single frame worth of payload and returns the encoded
// length. InvalidPayloadLengthError is recoverable, all other errors MUST be
// treated as fatal and the session aborted.
func (encoder *Encoder) Encode(frame, payload []byte) (n int, err error) {
payloadLen := len(payload)
if MaximumFramePayloadLength < payloadLen {
return 0, InvalidPayloadLengthError(payloadLen)
}
if len(frame) < payloadLen+FrameOverhead {
return 0, io.ErrShortBuffer
}
// Generate a new nonce.
var nonce [nonceLength]byte
if err = encoder.nonce.bytes(&nonce); err != nil {
return 0, err
}
encoder.nonce.counter++
// Encrypt and MAC payload.
box := secretbox.Seal(frame[:lengthLength], payload, &nonce, &encoder.key)
// Obfuscate the length.
length := uint16(len(box) - lengthLength)
lengthMask := encoder.drbg.NextBlock()
length ^= binary.BigEndian.Uint16(lengthMask)
binary.BigEndian.PutUint16(frame[:2], length)
// Return the frame.
return len(box), nil
}
// Decoder is a frame decoder instance.
type Decoder struct {
key [keyLength]byte
nonce boxNonce
drbg *drbg.HashDrbg
nextNonce [nonceLength]byte
nextLength uint16
nextLengthInvalid bool
}
// NewDecoder creates a new Decoder instance. It must be supplied a slice
// containing exactly KeyLength bytes of keying material.
func NewDecoder(key []byte) *Decoder {
if len(key) != KeyLength {
panic(fmt.Sprintf("BUG: Invalid decoder key length: %d", len(key)))
}
decoder := new(Decoder)
copy(decoder.key[:], key[0:keyLength])
decoder.nonce.init(key[keyLength : keyLength+noncePrefixLength])
seed, err := drbg.SeedFromBytes(key[keyLength+noncePrefixLength:])
if err != nil {
panic(fmt.Sprintf("BUG: Failed to initialize DRBG: %s", err))
}
decoder.drbg, _ = drbg.NewHashDrbg(seed)
return decoder
}
// Decode decodes a stream of data and returns the length if any. ErrAgain is
// a temporary failure, all other errors MUST be treated as fatal and the
// session aborted.
func (decoder *Decoder) Decode(data []byte, frames *bytes.Buffer) (int, error) {
// A length of 0 indicates that we do not know how big the next frame is
// going to be.
if decoder.nextLength == 0 {
// Attempt to pull out the next frame length.
if lengthLength > frames.Len() {
return 0, ErrAgain
}
// Remove the length field from the buffer.
var obfsLen [lengthLength]byte
_, err := io.ReadFull(frames, obfsLen[:])
if err != nil {
return 0, err
}
// Derive the nonce the peer used.
if err = decoder.nonce.bytes(&decoder.nextNonce); err != nil {
return 0, err
}
// Deobfuscate the length field.
length := binary.BigEndian.Uint16(obfsLen[:])
lengthMask := decoder.drbg.NextBlock()
length ^= binary.BigEndian.Uint16(lengthMask)
if maxFrameLength < length || minFrameLength > length {
// Per "Plaintext Recovery Attacks Against SSH" by
// Martin R. Albrecht, Kenneth G. Paterson and Gaven J. Watson,
// there are a class of attacks againt protocols that use similar
// sorts of framing schemes.
//
// While obfs4 should not allow plaintext recovery (CBC mode is
// not used), attempt to mitigate out of bound frame length errors
// by pretending that the length was a random valid range as per
// the countermeasure suggested by Denis Bider in section 6 of the
// paper.
decoder.nextLengthInvalid = true
length = uint16(csrand.IntRange(minFrameLength, maxFrameLength))
}
decoder.nextLength = length
}
if int(decoder.nextLength) > frames.Len() {
return 0, ErrAgain
}
// Unseal the frame.
var box [maxFrameLength]byte
n, err := io.ReadFull(frames, box[:decoder.nextLength])
if err != nil {
return 0, err
}
out, ok := secretbox.Open(data[:0], box[:n], &decoder.nextNonce, &decoder.key)
if !ok || decoder.nextLengthInvalid {
// When a random length is used (on length error) the tag should always
// mismatch, but be paranoid.
return 0, ErrTagMismatch
}
// Clean up and prepare for the next frame.
decoder.nextLength = 0
decoder.nonce.counter++
return len(out), nil
}

View File

@ -1,424 +0,0 @@
/*
* Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package obfs4
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"hash"
"strconv"
"time"
"git.torproject.org/pluggable-transports/obfs4.git/common/csrand"
"git.torproject.org/pluggable-transports/obfs4.git/common/ntor"
"git.torproject.org/pluggable-transports/obfs4.git/common/replayfilter"
"git.torproject.org/pluggable-transports/obfs4.git/transports/obfs4/framing"
)
const (
maxHandshakeLength = 8192
clientMinPadLength = (serverMinHandshakeLength + inlineSeedFrameLength) -
clientMinHandshakeLength
clientMaxPadLength = maxHandshakeLength - clientMinHandshakeLength
clientMinHandshakeLength = ntor.RepresentativeLength + markLength + macLength
serverMinPadLength = 0
serverMaxPadLength = maxHandshakeLength - (serverMinHandshakeLength +
inlineSeedFrameLength)
serverMinHandshakeLength = ntor.RepresentativeLength + ntor.AuthLength +
markLength + macLength
markLength = sha256.Size / 2
macLength = sha256.Size / 2
inlineSeedFrameLength = framing.FrameOverhead + packetOverhead + seedPacketPayloadLength
)
// ErrMarkNotFoundYet is the error returned when the obfs4 handshake is
// incomplete and requires more data to continue. This error is non-fatal and
// is the equivalent to EAGAIN/EWOULDBLOCK.
var ErrMarkNotFoundYet = errors.New("handshake: M_[C,S] not found yet")
// ErrInvalidHandshake is the error returned when the obfs4 handshake fails due
// to the peer not sending the correct mark. This error is fatal and the
// connection MUST be dropped.
var ErrInvalidHandshake = errors.New("handshake: Failed to find M_[C,S]")
// ErrReplayedHandshake is the error returned when the obfs4 handshake fails
// due it being replayed. This error is fatal and the connection MUST be
// dropped.
var ErrReplayedHandshake = errors.New("handshake: Replay detected")
// ErrNtorFailed is the error returned when the ntor handshake fails. This
// error is fatal and the connection MUST be dropped.
var ErrNtorFailed = errors.New("handshake: ntor handshake failure")
// InvalidMacError is the error returned when the handshake MACs do not match.
// This error is fatal and the connection MUST be dropped.
type InvalidMacError struct {
Derived []byte
Received []byte
}
func (e *InvalidMacError) Error() string {
return fmt.Sprintf("handshake: MAC mismatch: Dervied: %s Received: %s.",
hex.EncodeToString(e.Derived), hex.EncodeToString(e.Received))
}
// InvalidAuthError is the error returned when the ntor AUTH tags do not match.
// This error is fatal and the connection MUST be dropped.
type InvalidAuthError struct {
Derived *ntor.Auth
Received *ntor.Auth
}
func (e *InvalidAuthError) Error() string {
return fmt.Sprintf("handshake: ntor AUTH mismatch: Derived: %s Received:%s.",
hex.EncodeToString(e.Derived.Bytes()[:]),
hex.EncodeToString(e.Received.Bytes()[:]))
}
type clientHandshake struct {
keypair *ntor.Keypair
nodeID *ntor.NodeID
serverIdentity *ntor.PublicKey
epochHour []byte
padLen int
mac hash.Hash
serverRepresentative *ntor.Representative
serverAuth *ntor.Auth
serverMark []byte
}
func newClientHandshake(nodeID *ntor.NodeID, serverIdentity *ntor.PublicKey, sessionKey *ntor.Keypair) *clientHandshake {
hs := new(clientHandshake)
hs.keypair = sessionKey
hs.nodeID = nodeID
hs.serverIdentity = serverIdentity
hs.padLen = csrand.IntRange(clientMinPadLength, clientMaxPadLength)
hs.mac = hmac.New(sha256.New, append(hs.serverIdentity.Bytes()[:], hs.nodeID.Bytes()[:]...))
return hs
}
func (hs *clientHandshake) generateHandshake() ([]byte, error) {
var buf bytes.Buffer
hs.mac.Reset()
hs.mac.Write(hs.keypair.Representative().Bytes()[:])
mark := hs.mac.Sum(nil)[:markLength]
// The client handshake is X | P_C | M_C | MAC(X | P_C | M_C | E) where:
// * X is the client's ephemeral Curve25519 public key representative.
// * P_C is [clientMinPadLength,clientMaxPadLength] bytes of random padding.
// * M_C is HMAC-SHA256-128(serverIdentity | NodeID, X)
// * MAC is HMAC-SHA256-128(serverIdentity | NodeID, X .... E)
// * E is the string representation of the number of hours since the UNIX
// epoch.
// Generate the padding
pad, err := makePad(hs.padLen)
if err != nil {
return nil, err
}
// Write X, P_C, M_C.
buf.Write(hs.keypair.Representative().Bytes()[:])
buf.Write(pad)
buf.Write(mark)
// Calculate and write the MAC.
hs.mac.Reset()
hs.mac.Write(buf.Bytes())
hs.epochHour = []byte(strconv.FormatInt(getEpochHour(), 10))
hs.mac.Write(hs.epochHour)
buf.Write(hs.mac.Sum(nil)[:macLength])
return buf.Bytes(), nil
}
func (hs *clientHandshake) parseServerHandshake(resp []byte) (int, []byte, error) {
// No point in examining the data unless the miminum plausible response has
// been received.
if serverMinHandshakeLength > len(resp) {
return 0, nil, ErrMarkNotFoundYet
}
if hs.serverRepresentative == nil || hs.serverAuth == nil {
// Pull out the representative/AUTH. (XXX: Add ctors to ntor)
hs.serverRepresentative = new(ntor.Representative)
copy(hs.serverRepresentative.Bytes()[:], resp[0:ntor.RepresentativeLength])
hs.serverAuth = new(ntor.Auth)
copy(hs.serverAuth.Bytes()[:], resp[ntor.RepresentativeLength:])
// Derive the mark.
hs.mac.Reset()
hs.mac.Write(hs.serverRepresentative.Bytes()[:])
hs.serverMark = hs.mac.Sum(nil)[:markLength]
}
// Attempt to find the mark + MAC.
pos := findMarkMac(hs.serverMark, resp, ntor.RepresentativeLength+ntor.AuthLength+serverMinPadLength,
maxHandshakeLength, false)
if pos == -1 {
if len(resp) >= maxHandshakeLength {
return 0, nil, ErrInvalidHandshake
}
return 0, nil, ErrMarkNotFoundYet
}
// Validate the MAC.
hs.mac.Reset()
hs.mac.Write(resp[:pos+markLength])
hs.mac.Write(hs.epochHour)
macCmp := hs.mac.Sum(nil)[:macLength]
macRx := resp[pos+markLength : pos+markLength+macLength]
if !hmac.Equal(macCmp, macRx) {
return 0, nil, &InvalidMacError{macCmp, macRx}
}
// Complete the handshake.
serverPublic := hs.serverRepresentative.ToPublic()
ok, seed, auth := ntor.ClientHandshake(hs.keypair, serverPublic,
hs.serverIdentity, hs.nodeID)
if !ok {
return 0, nil, ErrNtorFailed
}
if !ntor.CompareAuth(auth, hs.serverAuth.Bytes()[:]) {
return 0, nil, &InvalidAuthError{auth, hs.serverAuth}
}
return pos + markLength + macLength, seed.Bytes()[:], nil
}
type serverHandshake struct {
keypair *ntor.Keypair
nodeID *ntor.NodeID
serverIdentity *ntor.Keypair
epochHour []byte
serverAuth *ntor.Auth
padLen int
mac hash.Hash
clientRepresentative *ntor.Representative
clientMark []byte
}
func newServerHandshake(nodeID *ntor.NodeID, serverIdentity *ntor.Keypair, sessionKey *ntor.Keypair) *serverHandshake {
hs := new(serverHandshake)
hs.keypair = sessionKey
hs.nodeID = nodeID
hs.serverIdentity = serverIdentity
hs.padLen = csrand.IntRange(serverMinPadLength, serverMaxPadLength)
hs.mac = hmac.New(sha256.New, append(hs.serverIdentity.Public().Bytes()[:], hs.nodeID.Bytes()[:]...))
return hs
}
func (hs *serverHandshake) parseClientHandshake(filter *replayfilter.ReplayFilter, resp []byte) ([]byte, error) {
// No point in examining the data unless the miminum plausible response has
// been received.
if clientMinHandshakeLength > len(resp) {
return nil, ErrMarkNotFoundYet
}
if hs.clientRepresentative == nil {
// Pull out the representative/AUTH. (XXX: Add ctors to ntor)
hs.clientRepresentative = new(ntor.Representative)
copy(hs.clientRepresentative.Bytes()[:], resp[0:ntor.RepresentativeLength])
// Derive the mark.
hs.mac.Reset()
hs.mac.Write(hs.clientRepresentative.Bytes()[:])
hs.clientMark = hs.mac.Sum(nil)[:markLength]
}
// Attempt to find the mark + MAC.
pos := findMarkMac(hs.clientMark, resp, ntor.RepresentativeLength+clientMinPadLength,
maxHandshakeLength, true)
if pos == -1 {
if len(resp) >= maxHandshakeLength {
return nil, ErrInvalidHandshake
}
return nil, ErrMarkNotFoundYet
}
// Validate the MAC.
macFound := false
for _, off := range []int64{0, -1, 1} {
// Allow epoch to be off by up to a hour in either direction.
epochHour := []byte(strconv.FormatInt(getEpochHour()+int64(off), 10))
hs.mac.Reset()
hs.mac.Write(resp[:pos+markLength])
hs.mac.Write(epochHour)
macCmp := hs.mac.Sum(nil)[:macLength]
macRx := resp[pos+markLength : pos+markLength+macLength]
if hmac.Equal(macCmp, macRx) {
// Ensure that this handshake has not been seen previously.
if filter.TestAndSet(time.Now(), macRx) {
// The client either happened to generate exactly the same
// session key and padding, or someone is replaying a previous
// handshake. In either case, fuck them.
return nil, ErrReplayedHandshake
}
macFound = true
hs.epochHour = epochHour
// We could break out here, but in the name of reducing timing
// variation, evaluate all 3 MACs.
}
}
if !macFound {
// This probably should be an InvalidMacError, but conveying the 3 MACS
// that would be accepted is annoying so just return a generic fatal
// failure.
return nil, ErrInvalidHandshake
}
// Client should never sent trailing garbage.
if len(resp) != pos+markLength+macLength {
return nil, ErrInvalidHandshake
}
clientPublic := hs.clientRepresentative.ToPublic()
ok, seed, auth := ntor.ServerHandshake(clientPublic, hs.keypair,
hs.serverIdentity, hs.nodeID)
if !ok {
return nil, ErrNtorFailed
}
hs.serverAuth = auth
return seed.Bytes()[:], nil
}
func (hs *serverHandshake) generateHandshake() ([]byte, error) {
var buf bytes.Buffer
hs.mac.Reset()
hs.mac.Write(hs.keypair.Representative().Bytes()[:])
mark := hs.mac.Sum(nil)[:markLength]
// The server handshake is Y | AUTH | P_S | M_S | MAC(Y | AUTH | P_S | M_S | E) where:
// * Y is the server's ephemeral Curve25519 public key representative.
// * AUTH is the ntor handshake AUTH value.
// * P_S is [serverMinPadLength,serverMaxPadLength] bytes of random padding.
// * M_S is HMAC-SHA256-128(serverIdentity | NodeID, Y)
// * MAC is HMAC-SHA256-128(serverIdentity | NodeID, Y .... E)
// * E is the string representation of the number of hours since the UNIX
// epoch.
// Generate the padding
pad, err := makePad(hs.padLen)
if err != nil {
return nil, err
}
// Write Y, AUTH, P_S, M_S.
buf.Write(hs.keypair.Representative().Bytes()[:])
buf.Write(hs.serverAuth.Bytes()[:])
buf.Write(pad)
buf.Write(mark)
// Calculate and write the MAC.
hs.mac.Reset()
hs.mac.Write(buf.Bytes())
hs.mac.Write(hs.epochHour) // Set in hs.parseClientHandshake()
buf.Write(hs.mac.Sum(nil)[:macLength])
return buf.Bytes(), nil
}
// getEpochHour returns the number of hours since the UNIX epoch.
func getEpochHour() int64 {
return time.Now().Unix() / 3600
}
func findMarkMac(mark, buf []byte, startPos, maxPos int, fromTail bool) (pos int) {
if len(mark) != markLength {
panic(fmt.Sprintf("BUG: Invalid mark length: %d", len(mark)))
}
endPos := len(buf)
if startPos > len(buf) {
return -1
}
if endPos > maxPos {
endPos = maxPos
}
if endPos-startPos < markLength+macLength {
return -1
}
if fromTail {
// The server can optimize the search process by only examining the
// tail of the buffer. The client can't send valid data past M_C |
// MAC_C as it does not have the server's public key yet.
pos = endPos - (markLength + macLength)
if !hmac.Equal(buf[pos:pos+markLength], mark) {
return -1
}
return
}
// The client has to actually do a substring search since the server can
// and will send payload trailing the response.
//
// XXX: bytes.Index() uses a naive search, which kind of sucks.
pos = bytes.Index(buf[startPos:endPos], mark)
if pos == -1 {
return -1
}
// Ensure that there is enough trailing data for the MAC.
if startPos+pos+markLength+macLength > endPos {
return -1
}
// Return the index relative to the start of the slice.
pos += startPos
return
}
func makePad(padLen int) ([]byte, error) {
pad := make([]byte, padLen)
if err := csrand.Bytes(pad); err != nil {
return nil, err
}
return pad, nil
}

View File

@ -1,647 +0,0 @@
/*
* Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
// Package obfs4 provides an implementation of the Tor Project's obfs4
// obfuscation protocol.
package obfs4
import (
"bytes"
"crypto/sha256"
"flag"
"fmt"
"math/rand"
"net"
"strconv"
"syscall"
"time"
"git.torproject.org/pluggable-transports/goptlib.git"
"git.torproject.org/pluggable-transports/obfs4.git/common/drbg"
"git.torproject.org/pluggable-transports/obfs4.git/common/ntor"
"git.torproject.org/pluggable-transports/obfs4.git/common/probdist"
"git.torproject.org/pluggable-transports/obfs4.git/common/replayfilter"
"git.torproject.org/pluggable-transports/obfs4.git/transports/base"
"git.torproject.org/pluggable-transports/obfs4.git/transports/obfs4/framing"
)
const (
transportName = "obfs4"
nodeIDArg = "node-id"
publicKeyArg = "public-key"
privateKeyArg = "private-key"
seedArg = "drbg-seed"
iatArg = "iat-mode"
certArg = "cert"
biasCmdArg = "obfs4-distBias"
seedLength = drbg.SeedLength
headerLength = framing.FrameOverhead + packetOverhead
clientHandshakeTimeout = time.Duration(60) * time.Second
serverHandshakeTimeout = time.Duration(30) * time.Second
replayTTL = time.Duration(3) * time.Hour
maxIATDelay = 100
maxCloseDelayBytes = maxHandshakeLength
maxCloseDelay = 60
)
const (
iatNone = iota
iatEnabled
iatParanoid
)
// biasedDist controls if the probability table will be ScrambleSuit style or
// uniformly distributed.
var biasedDist bool
type obfs4ClientArgs struct {
nodeID *ntor.NodeID
publicKey *ntor.PublicKey
sessionKey *ntor.Keypair
iatMode int
}
// Transport is the obfs4 implementation of the base.Transport interface.
type Transport struct{}
// Name returns the name of the obfs4 transport protocol.
func (t *Transport) Name() string {
return transportName
}
// ClientFactory returns a new obfs4ClientFactory instance.
func (t *Transport) ClientFactory(stateDir string) (base.ClientFactory, error) {
cf := &obfs4ClientFactory{transport: t}
return cf, nil
}
// ServerFactory returns a new obfs4ServerFactory instance.
func (t *Transport) ServerFactory(stateDir string, args *pt.Args) (base.ServerFactory, error) {
st, err := serverStateFromArgs(stateDir, args)
if err != nil {
return nil, err
}
var iatSeed *drbg.Seed
if st.iatMode != iatNone {
iatSeedSrc := sha256.Sum256(st.drbgSeed.Bytes()[:])
var err error
iatSeed, err = drbg.SeedFromBytes(iatSeedSrc[:])
if err != nil {
return nil, err
}
}
// Store the arguments that should appear in our descriptor for the clients.
ptArgs := pt.Args{}
ptArgs.Add(certArg, st.cert.String())
ptArgs.Add(iatArg, strconv.Itoa(st.iatMode))
// Initialize the replay filter.
filter, err := replayfilter.New(replayTTL)
if err != nil {
return nil, err
}
// Initialize the close thresholds for failed connections.
drbg, err := drbg.NewHashDrbg(st.drbgSeed)
if err != nil {
return nil, err
}
rng := rand.New(drbg)
sf := &obfs4ServerFactory{t, &ptArgs, st.nodeID, st.identityKey, st.drbgSeed, iatSeed, st.iatMode, filter, rng.Intn(maxCloseDelayBytes), rng.Intn(maxCloseDelay)}
return sf, nil
}
type obfs4ClientFactory struct {
transport base.Transport
}
func (cf *obfs4ClientFactory) Transport() base.Transport {
return cf.transport
}
func (cf *obfs4ClientFactory) ParseArgs(args *pt.Args) (interface{}, error) {
var nodeID *ntor.NodeID
var publicKey *ntor.PublicKey
// The "new" (version >= 0.0.3) bridge lines use a unified "cert" argument
// for the Node ID and Public Key.
certStr, ok := args.Get(certArg)
if ok {
cert, err := serverCertFromString(certStr)
if err != nil {
return nil, err
}
nodeID, publicKey = cert.unpack()
} else {
// The "old" style (version <= 0.0.2) bridge lines use separate Node ID
// and Public Key arguments in Base16 encoding and are a UX disaster.
nodeIDStr, ok := args.Get(nodeIDArg)
if !ok {
return nil, fmt.Errorf("missing argument '%s'", nodeIDArg)
}
var err error
if nodeID, err = ntor.NodeIDFromHex(nodeIDStr); err != nil {
return nil, err
}
publicKeyStr, ok := args.Get(publicKeyArg)
if !ok {
return nil, fmt.Errorf("missing argument '%s'", publicKeyArg)
}
if publicKey, err = ntor.PublicKeyFromHex(publicKeyStr); err != nil {
return nil, err
}
}
// IAT config is common across the two bridge line formats.
iatStr, ok := args.Get(iatArg)
if !ok {
return nil, fmt.Errorf("missing argument '%s'", iatArg)
}
iatMode, err := strconv.Atoi(iatStr)
if err != nil || iatMode < iatNone || iatMode > iatParanoid {
return nil, fmt.Errorf("invalid iat-mode '%d'", iatMode)
}
// Generate the session key pair before connectiong to hide the Elligator2
// rejection sampling from network observers.
sessionKey, err := ntor.NewKeypair(true)
if err != nil {
return nil, err
}
return &obfs4ClientArgs{nodeID, publicKey, sessionKey, iatMode}, nil
}
func (cf *obfs4ClientFactory) Dial(network, addr string, dialFn base.DialFunc, args interface{}) (net.Conn, error) {
// Validate args before bothering to open connection.
ca, ok := args.(*obfs4ClientArgs)
if !ok {
return nil, fmt.Errorf("invalid argument type for args")
}
conn, err := dialFn(network, addr)
if err != nil {
return nil, err
}
dialConn := conn
if conn, err = newObfs4ClientConn(conn, ca); err != nil {
dialConn.Close()
return nil, err
}
return conn, nil
}
type obfs4ServerFactory struct {
transport base.Transport
args *pt.Args
nodeID *ntor.NodeID
identityKey *ntor.Keypair
lenSeed *drbg.Seed
iatSeed *drbg.Seed
iatMode int
replayFilter *replayfilter.ReplayFilter
closeDelayBytes int
closeDelay int
}
func (sf *obfs4ServerFactory) Transport() base.Transport {
return sf.transport
}
func (sf *obfs4ServerFactory) Args() *pt.Args {
return sf.args
}
func (sf *obfs4ServerFactory) WrapConn(conn net.Conn) (net.Conn, error) {
// Not much point in having a separate newObfs4ServerConn routine when
// wrapping requires using values from the factory instance.
// Generate the session keypair *before* consuming data from the peer, to
// attempt to mask the rejection sampling due to use of Elligator2. This
// might be futile, but the timing differential isn't very large on modern
// hardware, and there are far easier statistical attacks that can be
// mounted as a distinguisher.
sessionKey, err := ntor.NewKeypair(true)
if err != nil {
return nil, err
}
lenDist := probdist.New(sf.lenSeed, 0, framing.MaximumSegmentLength, biasedDist)
var iatDist *probdist.WeightedDist
if sf.iatSeed != nil {
iatDist = probdist.New(sf.iatSeed, 0, maxIATDelay, biasedDist)
}
c := &obfs4Conn{conn, true, lenDist, iatDist, sf.iatMode, bytes.NewBuffer(nil), bytes.NewBuffer(nil), make([]byte, consumeReadSize), nil, nil}
startTime := time.Now()
if err = c.serverHandshake(sf, sessionKey); err != nil {
c.closeAfterDelay(sf, startTime)
return nil, err
}
return c, nil
}
type obfs4Conn struct {
net.Conn
isServer bool
lenDist *probdist.WeightedDist
iatDist *probdist.WeightedDist
iatMode int
receiveBuffer *bytes.Buffer
receiveDecodedBuffer *bytes.Buffer
readBuffer []byte
encoder *framing.Encoder
decoder *framing.Decoder
}
func newObfs4ClientConn(conn net.Conn, args *obfs4ClientArgs) (c *obfs4Conn, err error) {
// Generate the initial protocol polymorphism distribution(s).
var seed *drbg.Seed
if seed, err = drbg.NewSeed(); err != nil {
return
}
lenDist := probdist.New(seed, 0, framing.MaximumSegmentLength, biasedDist)
var iatDist *probdist.WeightedDist
if args.iatMode != iatNone {
var iatSeed *drbg.Seed
iatSeedSrc := sha256.Sum256(seed.Bytes()[:])
if iatSeed, err = drbg.SeedFromBytes(iatSeedSrc[:]); err != nil {
return
}
iatDist = probdist.New(iatSeed, 0, maxIATDelay, biasedDist)
}
// Allocate the client structure.
c = &obfs4Conn{conn, false, lenDist, iatDist, args.iatMode, bytes.NewBuffer(nil), bytes.NewBuffer(nil), make([]byte, consumeReadSize), nil, nil}
// Start the handshake timeout.
deadline := time.Now().Add(clientHandshakeTimeout)
if err = conn.SetDeadline(deadline); err != nil {
return nil, err
}
if err = c.clientHandshake(args.nodeID, args.publicKey, args.sessionKey); err != nil {
return nil, err
}
// Stop the handshake timeout.
if err = conn.SetDeadline(time.Time{}); err != nil {
return nil, err
}
return
}
func (conn *obfs4Conn) clientHandshake(nodeID *ntor.NodeID, peerIdentityKey *ntor.PublicKey, sessionKey *ntor.Keypair) error {
if conn.isServer {
return fmt.Errorf("clientHandshake called on server connection")
}
// Generate and send the client handshake.
hs := newClientHandshake(nodeID, peerIdentityKey, sessionKey)
blob, err := hs.generateHandshake()
if err != nil {
return err
}
if _, err = conn.Conn.Write(blob); err != nil {
return err
}
// Consume the server handshake.
var hsBuf [maxHandshakeLength]byte
for {
n, err := conn.Conn.Read(hsBuf[:])
if err != nil {
// The Read() could have returned data and an error, but there is
// no point in continuing on an EOF or whatever.
return err
}
conn.receiveBuffer.Write(hsBuf[:n])
n, seed, err := hs.parseServerHandshake(conn.receiveBuffer.Bytes())
if err == ErrMarkNotFoundYet {
continue
} else if err != nil {
return err
}
_ = conn.receiveBuffer.Next(n)
// Use the derived key material to intialize the link crypto.
okm := ntor.Kdf(seed, framing.KeyLength*2)
conn.encoder = framing.NewEncoder(okm[:framing.KeyLength])
conn.decoder = framing.NewDecoder(okm[framing.KeyLength:])
return nil
}
}
func (conn *obfs4Conn) serverHandshake(sf *obfs4ServerFactory, sessionKey *ntor.Keypair) error {
if !conn.isServer {
return fmt.Errorf("serverHandshake called on client connection")
}
// Generate the server handshake, and arm the base timeout.
hs := newServerHandshake(sf.nodeID, sf.identityKey, sessionKey)
if err := conn.Conn.SetDeadline(time.Now().Add(serverHandshakeTimeout)); err != nil {
return err
}
// Consume the client handshake.
var hsBuf [maxHandshakeLength]byte
for {
n, err := conn.Conn.Read(hsBuf[:])
if err != nil {
// The Read() could have returned data and an error, but there is
// no point in continuing on an EOF or whatever.
return err
}
conn.receiveBuffer.Write(hsBuf[:n])
seed, err := hs.parseClientHandshake(sf.replayFilter, conn.receiveBuffer.Bytes())
if err == ErrMarkNotFoundYet {
continue
} else if err != nil {
return err
}
conn.receiveBuffer.Reset()
if err := conn.Conn.SetDeadline(time.Time{}); err != nil {
return nil
}
// Use the derived key material to intialize the link crypto.
okm := ntor.Kdf(seed, framing.KeyLength*2)
conn.encoder = framing.NewEncoder(okm[framing.KeyLength:])
conn.decoder = framing.NewDecoder(okm[:framing.KeyLength])
break
}
// Since the current and only implementation always sends a PRNG seed for
// the length obfuscation, this makes the amount of data received from the
// server inconsistent with the length sent from the client.
//
// Rebalance this by tweaking the client mimimum padding/server maximum
// padding, and sending the PRNG seed unpadded (As in, treat the PRNG seed
// as part of the server response). See inlineSeedFrameLength in
// handshake_ntor.go.
// Generate/send the response.
blob, err := hs.generateHandshake()
if err != nil {
return err
}
var frameBuf bytes.Buffer
if _, err = frameBuf.Write(blob); err != nil {
return err
}
// Send the PRNG seed as the first packet.
if err := conn.makePacket(&frameBuf, packetTypePrngSeed, sf.lenSeed.Bytes()[:], 0); err != nil {
return err
}
if _, err = conn.Conn.Write(frameBuf.Bytes()); err != nil {
return err
}
return nil
}
func (conn *obfs4Conn) Read(b []byte) (n int, err error) {
// If there is no payload from the previous Read() calls, consume data off
// the network. Not all data received is guaranteed to be usable payload,
// so do this in a loop till data is present or an error occurs.
for conn.receiveDecodedBuffer.Len() == 0 {
err = conn.readPackets()
if err == framing.ErrAgain {
// Don't proagate this back up the call stack if we happen to break
// out of the loop.
err = nil
continue
} else if err != nil {
break
}
}
// Even if err is set, attempt to do the read anyway so that all decoded
// data gets relayed before the connection is torn down.
if conn.receiveDecodedBuffer.Len() > 0 {
var berr error
n, berr = conn.receiveDecodedBuffer.Read(b)
if err == nil {
// Only propagate berr if there are not more important (fatal)
// errors from the network/crypto/packet processing.
err = berr
}
}
return
}
func (conn *obfs4Conn) Write(b []byte) (n int, err error) {
chopBuf := bytes.NewBuffer(b)
var payload [maxPacketPayloadLength]byte
var frameBuf bytes.Buffer
// Chop the pending data into payload frames.
for chopBuf.Len() > 0 {
// Send maximum sized frames.
rdLen := 0
rdLen, err = chopBuf.Read(payload[:])
if err != nil {
return 0, err
} else if rdLen == 0 {
panic(fmt.Sprintf("BUG: Write(), chopping length was 0"))
}
n += rdLen
err = conn.makePacket(&frameBuf, packetTypePayload, payload[:rdLen], 0)
if err != nil {
return 0, err
}
}
if conn.iatMode != iatParanoid {
// For non-paranoid IAT, pad once per burst. Paranoid IAT handles
// things differently.
if err = conn.padBurst(&frameBuf, conn.lenDist.Sample()); err != nil {
return 0, err
}
}
// Write the pending data onto the network. Partial writes are fatal,
// because the frame encoder state is advanced, and the code doesn't keep
// frameBuf around. In theory, write timeouts and whatnot could be
// supported if this wasn't the case, but that complicates the code.
if conn.iatMode != iatNone {
var iatFrame [framing.MaximumSegmentLength]byte
for frameBuf.Len() > 0 {
iatWrLen := 0
switch conn.iatMode {
case iatEnabled:
// Standard (ScrambleSuit-style) IAT obfuscation optimizes for
// bulk transport and will write ~MTU sized frames when
// possible.
iatWrLen, err = frameBuf.Read(iatFrame[:])
case iatParanoid:
// Paranoid IAT obfuscation throws performance out of the
// window and will sample the length distribution every time a
// write is scheduled.
targetLen := conn.lenDist.Sample()
if frameBuf.Len() < targetLen {
// There's not enough data buffered for the target write,
// so padding must be inserted.
if err = conn.padBurst(&frameBuf, targetLen); err != nil {
return 0, err
}
if frameBuf.Len() != targetLen {
// Ugh, padding came out to a value that required more
// than one frame, this is relatively unlikely so just
// resample since there's enough data to ensure that
// the next sample will be written.
continue
}
}
iatWrLen, err = frameBuf.Read(iatFrame[:targetLen])
}
if err != nil {
return 0, err
} else if iatWrLen == 0 {
panic(fmt.Sprintf("BUG: Write(), iat length was 0"))
}
// Calculate the delay. The delay resolution is 100 usec, leading
// to a maximum delay of 10 msec.
iatDelta := time.Duration(conn.iatDist.Sample() * 100)
// Write then sleep.
_, err = conn.Conn.Write(iatFrame[:iatWrLen])
if err != nil {
return 0, err
}
time.Sleep(iatDelta * time.Microsecond)
}
} else {
_, err = conn.Conn.Write(frameBuf.Bytes())
}
return
}
func (conn *obfs4Conn) SetDeadline(t time.Time) error {
return syscall.ENOTSUP
}
func (conn *obfs4Conn) SetWriteDeadline(t time.Time) error {
return syscall.ENOTSUP
}
func (conn *obfs4Conn) closeAfterDelay(sf *obfs4ServerFactory, startTime time.Time) {
// I-it's not like I w-wanna handshake with you or anything. B-b-baka!
defer conn.Conn.Close()
delay := time.Duration(sf.closeDelay)*time.Second + serverHandshakeTimeout
deadline := startTime.Add(delay)
if time.Now().After(deadline) {
return
}
if err := conn.Conn.SetReadDeadline(deadline); err != nil {
return
}
// Consume and discard data on this connection until either the specified
// interval passes or a certain size has been reached.
discarded := 0
var buf [framing.MaximumSegmentLength]byte
for discarded < int(sf.closeDelayBytes) {
n, err := conn.Conn.Read(buf[:])
if err != nil {
return
}
discarded += n
}
}
func (conn *obfs4Conn) padBurst(burst *bytes.Buffer, toPadTo int) (err error) {
tailLen := burst.Len() % framing.MaximumSegmentLength
padLen := 0
if toPadTo >= tailLen {
padLen = toPadTo - tailLen
} else {
padLen = (framing.MaximumSegmentLength - tailLen) + toPadTo
}
if padLen > headerLength {
err = conn.makePacket(burst, packetTypePayload, []byte{},
uint16(padLen-headerLength))
if err != nil {
return
}
} else if padLen > 0 {
err = conn.makePacket(burst, packetTypePayload, []byte{},
maxPacketPayloadLength)
if err != nil {
return
}
err = conn.makePacket(burst, packetTypePayload, []byte{},
uint16(padLen))
if err != nil {
return
}
}
return
}
func init() {
flag.BoolVar(&biasedDist, biasCmdArg, false, "Enable obfs4 using ScrambleSuit style table generation")
}
var _ base.ClientFactory = (*obfs4ClientFactory)(nil)
var _ base.ServerFactory = (*obfs4ServerFactory)(nil)
var _ base.Transport = (*Transport)(nil)
var _ net.Conn = (*obfs4Conn)(nil)

View File

@ -1,175 +0,0 @@
/*
* Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package obfs4
import (
"crypto/sha256"
"encoding/binary"
"fmt"
"io"
"git.torproject.org/pluggable-transports/obfs4.git/common/drbg"
"git.torproject.org/pluggable-transports/obfs4.git/transports/obfs4/framing"
)
const (
packetOverhead = 2 + 1
maxPacketPayloadLength = framing.MaximumFramePayloadLength - packetOverhead
maxPacketPaddingLength = maxPacketPayloadLength
seedPacketPayloadLength = seedLength
consumeReadSize = framing.MaximumSegmentLength * 16
)
const (
packetTypePayload = iota
packetTypePrngSeed
)
// InvalidPacketLengthError is the error returned when decodePacket detects a
// invalid packet length/
type InvalidPacketLengthError int
func (e InvalidPacketLengthError) Error() string {
return fmt.Sprintf("packet: Invalid packet length: %d", int(e))
}
// InvalidPayloadLengthError is the error returned when decodePacket rejects the
// payload length.
type InvalidPayloadLengthError int
func (e InvalidPayloadLengthError) Error() string {
return fmt.Sprintf("packet: Invalid payload length: %d", int(e))
}
var zeroPadBytes [maxPacketPaddingLength]byte
func (conn *obfs4Conn) makePacket(w io.Writer, pktType uint8, data []byte, padLen uint16) error {
var pkt [framing.MaximumFramePayloadLength]byte
if len(data)+int(padLen) > maxPacketPayloadLength {
panic(fmt.Sprintf("BUG: makePacket() len(data) + padLen > maxPacketPayloadLength: %d + %d > %d",
len(data), padLen, maxPacketPayloadLength))
}
// Packets are:
// uint8_t type packetTypePayload (0x00)
// uint16_t length Length of the payload (Big Endian).
// uint8_t[] payload Data payload.
// uint8_t[] padding Padding.
pkt[0] = pktType
binary.BigEndian.PutUint16(pkt[1:], uint16(len(data)))
if len(data) > 0 {
copy(pkt[3:], data[:])
}
copy(pkt[3+len(data):], zeroPadBytes[:padLen])
pktLen := packetOverhead + len(data) + int(padLen)
// Encode the packet in an AEAD frame.
var frame [framing.MaximumSegmentLength]byte
frameLen, err := conn.encoder.Encode(frame[:], pkt[:pktLen])
if err != nil {
// All encoder errors are fatal.
return err
}
wrLen, err := w.Write(frame[:frameLen])
if err != nil {
return err
} else if wrLen < frameLen {
return io.ErrShortWrite
}
return nil
}
func (conn *obfs4Conn) readPackets() (err error) {
// Attempt to read off the network.
rdLen, rdErr := conn.Conn.Read(conn.readBuffer)
conn.receiveBuffer.Write(conn.readBuffer[:rdLen])
var decoded [framing.MaximumFramePayloadLength]byte
for conn.receiveBuffer.Len() > 0 {
// Decrypt an AEAD frame.
decLen := 0
decLen, err = conn.decoder.Decode(decoded[:], conn.receiveBuffer)
if err == framing.ErrAgain {
break
} else if err != nil {
break
} else if decLen < packetOverhead {
err = InvalidPacketLengthError(decLen)
break
}
// Decode the packet.
pkt := decoded[0:decLen]
pktType := pkt[0]
payloadLen := binary.BigEndian.Uint16(pkt[1:])
if int(payloadLen) > len(pkt)-packetOverhead {
err = InvalidPayloadLengthError(int(payloadLen))
break
}
payload := pkt[3 : 3+payloadLen]
switch pktType {
case packetTypePayload:
if payloadLen > 0 {
conn.receiveDecodedBuffer.Write(payload)
}
case packetTypePrngSeed:
// Only regenerate the distribution if we are the client.
if len(payload) == seedPacketPayloadLength && !conn.isServer {
var seed *drbg.Seed
seed, err = drbg.SeedFromBytes(payload)
if err != nil {
break
}
conn.lenDist.Reset(seed)
if conn.iatDist != nil {
iatSeedSrc := sha256.Sum256(seed.Bytes()[:])
iatSeed, err := drbg.SeedFromBytes(iatSeedSrc[:])
if err != nil {
break
}
conn.iatDist.Reset(iatSeed)
}
}
default:
// Ignore unknown packet types.
}
}
// Read errors (all fatal) take priority over various frame processing
// errors.
if rdErr != nil {
return rdErr
}
return
}

View File

@ -1,260 +0,0 @@
/*
* Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package obfs4
import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path"
"strconv"
"strings"
"git.torproject.org/pluggable-transports/goptlib.git"
"git.torproject.org/pluggable-transports/obfs4.git/common/csrand"
"git.torproject.org/pluggable-transports/obfs4.git/common/drbg"
"git.torproject.org/pluggable-transports/obfs4.git/common/ntor"
)
const (
stateFile = "obfs4_state.json"
bridgeFile = "obfs4_bridgeline.txt"
certSuffix = "=="
certLength = ntor.NodeIDLength + ntor.PublicKeyLength
)
type jsonServerState struct {
NodeID string `json:"node-id"`
PrivateKey string `json:"private-key"`
PublicKey string `json:"public-key"`
DrbgSeed string `json:"drbg-seed"`
IATMode int `json:"iat-mode"`
}
type obfs4ServerCert struct {
raw []byte
}
func (cert *obfs4ServerCert) String() string {
return strings.TrimSuffix(base64.StdEncoding.EncodeToString(cert.raw), certSuffix)
}
func (cert *obfs4ServerCert) unpack() (*ntor.NodeID, *ntor.PublicKey) {
if len(cert.raw) != certLength {
panic(fmt.Sprintf("cert length %d is invalid", len(cert.raw)))
}
nodeID, _ := ntor.NewNodeID(cert.raw[:ntor.NodeIDLength])
pubKey, _ := ntor.NewPublicKey(cert.raw[ntor.NodeIDLength:])
return nodeID, pubKey
}
func serverCertFromString(encoded string) (*obfs4ServerCert, error) {
decoded, err := base64.StdEncoding.DecodeString(encoded + certSuffix)
if err != nil {
return nil, fmt.Errorf("failed to decode cert: %s", err)
}
if len(decoded) != certLength {
return nil, fmt.Errorf("cert length %d is invalid", len(decoded))
}
return &obfs4ServerCert{raw: decoded}, nil
}
func serverCertFromState(st *obfs4ServerState) *obfs4ServerCert {
cert := new(obfs4ServerCert)
cert.raw = append(st.nodeID.Bytes()[:], st.identityKey.Public().Bytes()[:]...)
return cert
}
type obfs4ServerState struct {
nodeID *ntor.NodeID
identityKey *ntor.Keypair
drbgSeed *drbg.Seed
iatMode int
cert *obfs4ServerCert
}
func (st *obfs4ServerState) clientString() string {
return fmt.Sprintf("%s=%s %s=%d", certArg, st.cert, iatArg, st.iatMode)
}
func serverStateFromArgs(stateDir string, args *pt.Args) (*obfs4ServerState, error) {
var js jsonServerState
var nodeIDOk, privKeyOk, seedOk bool
js.NodeID, nodeIDOk = args.Get(nodeIDArg)
js.PrivateKey, privKeyOk = args.Get(privateKeyArg)
js.DrbgSeed, seedOk = args.Get(seedArg)
iatStr, iatOk := args.Get(iatArg)
// Either a private key, node id, and seed are ALL specified, or
// they should be loaded from the state file.
if !privKeyOk && !nodeIDOk && !seedOk {
if err := jsonServerStateFromFile(stateDir, &js); err != nil {
return nil, err
}
} else if !privKeyOk {
return nil, fmt.Errorf("missing argument '%s'", privateKeyArg)
} else if !nodeIDOk {
return nil, fmt.Errorf("missing argument '%s'", nodeIDArg)
} else if !seedOk {
return nil, fmt.Errorf("missing argument '%s'", seedArg)
}
// The IAT mode should be independently configurable.
if iatOk {
// If the IAT mode is specified, attempt to parse and apply it
// as an override.
iatMode, err := strconv.Atoi(iatStr)
if err != nil {
return nil, fmt.Errorf("malformed iat-mode '%s'", iatStr)
}
js.IATMode = iatMode
}
return serverStateFromJSONServerState(stateDir, &js)
}
func serverStateFromJSONServerState(stateDir string, js *jsonServerState) (*obfs4ServerState, error) {
var err error
st := new(obfs4ServerState)
if st.nodeID, err = ntor.NodeIDFromHex(js.NodeID); err != nil {
return nil, err
}
if st.identityKey, err = ntor.KeypairFromHex(js.PrivateKey); err != nil {
return nil, err
}
if st.drbgSeed, err = drbg.SeedFromHex(js.DrbgSeed); err != nil {
return nil, err
}
if js.IATMode < iatNone || js.IATMode > iatParanoid {
return nil, fmt.Errorf("invalid iat-mode '%d'", js.IATMode)
}
st.iatMode = js.IATMode
st.cert = serverCertFromState(st)
// Generate a human readable summary of the configured endpoint.
if err = newBridgeFile(stateDir, st); err != nil {
return nil, err
}
// Write back the possibly updated server state.
return st, writeJSONServerState(stateDir, js)
}
func jsonServerStateFromFile(stateDir string, js *jsonServerState) error {
fPath := path.Join(stateDir, stateFile)
f, err := ioutil.ReadFile(fPath)
if err != nil {
if os.IsNotExist(err) {
if err = newJSONServerState(stateDir, js); err == nil {
return nil
}
}
return err
}
if err := json.Unmarshal(f, js); err != nil {
return fmt.Errorf("failed to load statefile '%s': %s", fPath, err)
}
return nil
}
func newJSONServerState(stateDir string, js *jsonServerState) (err error) {
// Generate everything a server needs, using the cryptographic PRNG.
var st obfs4ServerState
rawID := make([]byte, ntor.NodeIDLength)
if err = csrand.Bytes(rawID); err != nil {
return
}
if st.nodeID, err = ntor.NewNodeID(rawID); err != nil {
return
}
if st.identityKey, err = ntor.NewKeypair(false); err != nil {
return
}
if st.drbgSeed, err = drbg.NewSeed(); err != nil {
return
}
st.iatMode = iatNone
// Encode it into JSON format and write the state file.
js.NodeID = st.nodeID.Hex()
js.PrivateKey = st.identityKey.Private().Hex()
js.PublicKey = st.identityKey.Public().Hex()
js.DrbgSeed = st.drbgSeed.Hex()
js.IATMode = st.iatMode
return writeJSONServerState(stateDir, js)
}
func writeJSONServerState(stateDir string, js *jsonServerState) error {
var err error
var encoded []byte
if encoded, err = json.Marshal(js); err != nil {
return err
}
if err = ioutil.WriteFile(path.Join(stateDir, stateFile), encoded, 0600); err != nil {
return err
}
return nil
}
func newBridgeFile(stateDir string, st *obfs4ServerState) error {
const prefix = "# obfs4 torrc client bridge line\n" +
"#\n" +
"# This file is an automatically generated bridge line based on\n" +
"# the current obfs4proxy configuration. EDITING IT WILL HAVE\n" +
"# NO EFFECT.\n" +
"#\n" +
"# Before distributing this Bridge, edit the placeholder fields\n" +
"# to contain the actual values:\n" +
"# <IP ADDRESS> - The public IP address of your obfs4 bridge.\n" +
"# <PORT> - The TCP/IP port of your obfs4 bridge.\n" +
"# <FINGERPRINT> - The bridge's fingerprint.\n\n"
bridgeLine := fmt.Sprintf("Bridge obfs4 <IP ADDRESS>:<PORT> <FINGERPRINT> %s\n",
st.clientString())
tmp := []byte(prefix + bridgeLine)
if err := ioutil.WriteFile(path.Join(stateDir, bridgeFile), tmp, 0600); err != nil {
return err
}
return nil
}

View File

@ -1,122 +0,0 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

View File

@ -1,14 +0,0 @@
### chacha20 - ChaCha20
#### Yawning Angel (yawning at schwanenlied dot me)
Yet another Go ChaCha20 implementation. Everything else I found was slow,
didn't support all the variants I need to use, or relied on cgo to go fast.
Features:
* 20 round, 256 bit key only. Everything else is pointless and stupid.
* IETF 96 bit nonce variant.
* XChaCha 24 byte nonce variant.
* SSE2 and AVX2 support on amd64 targets.
* Incremental encrypt/decrypt support, unlike golang.org/x/crypto/salsa20.

View File

@ -1,273 +0,0 @@
// chacha20.go - A ChaCha stream cipher implementation.
//
// To the extent possible under law, Yawning Angel has waived all copyright
// and related or neighboring rights to chacha20, using the Creative
// Commons "CC0" public domain dedication. See LICENSE or
// <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
package chacha20
import (
"crypto/cipher"
"encoding/binary"
"errors"
"math"
"runtime"
)
const (
// KeySize is the ChaCha20 key size in bytes.
KeySize = 32
// NonceSize is the ChaCha20 nonce size in bytes.
NonceSize = 8
// INonceSize is the IETF ChaCha20 nonce size in bytes.
INonceSize = 12
// XNonceSize is the XChaCha20 nonce size in bytes.
XNonceSize = 24
// HNonceSize is the HChaCha20 nonce size in bytes.
HNonceSize = 16
// BlockSize is the ChaCha20 block size in bytes.
BlockSize = 64
stateSize = 16
chachaRounds = 20
// The constant "expand 32-byte k" as little endian uint32s.
sigma0 = uint32(0x61707865)
sigma1 = uint32(0x3320646e)
sigma2 = uint32(0x79622d32)
sigma3 = uint32(0x6b206574)
)
var (
// ErrInvalidKey is the error returned when the key is invalid.
ErrInvalidKey = errors.New("key length must be KeySize bytes")
// ErrInvalidNonce is the error returned when the nonce is invalid.
ErrInvalidNonce = errors.New("nonce length must be NonceSize/INonceSize/XNonceSize bytes")
// ErrInvalidCounter is the error returned when the counter is invalid.
ErrInvalidCounter = errors.New("block counter is invalid (out of range)")
useUnsafe = false
usingVectors = false
blocksFn = blocksRef
)
// A Cipher is an instance of ChaCha20/XChaCha20 using a particular key and
// nonce.
type Cipher struct {
state [stateSize]uint32
buf [BlockSize]byte
off int
ietf bool
}
// Reset zeros the key data so that it will no longer appear in the process's
// memory.
func (c *Cipher) Reset() {
for i := range c.state {
c.state[i] = 0
}
for i := range c.buf {
c.buf[i] = 0
}
}
// XORKeyStream sets dst to the result of XORing src with the key stream. Dst
// and src may be the same slice but otherwise should not overlap.
func (c *Cipher) XORKeyStream(dst, src []byte) {
if len(dst) < len(src) {
src = src[:len(dst)]
}
for remaining := len(src); remaining > 0; {
// Process multiple blocks at once.
if c.off == BlockSize {
nrBlocks := remaining / BlockSize
directBytes := nrBlocks * BlockSize
if nrBlocks > 0 {
blocksFn(&c.state, src, dst, nrBlocks, c.ietf)
remaining -= directBytes
if remaining == 0 {
return
}
dst = dst[directBytes:]
src = src[directBytes:]
}
// If there's a partial block, generate 1 block of keystream into
// the internal buffer.
blocksFn(&c.state, nil, c.buf[:], 1, c.ietf)
c.off = 0
}
// Process partial blocks from the buffered keystream.
toXor := BlockSize - c.off
if remaining < toXor {
toXor = remaining
}
if toXor > 0 {
for i, v := range src[:toXor] {
dst[i] = v ^ c.buf[c.off+i]
}
dst = dst[toXor:]
src = src[toXor:]
remaining -= toXor
c.off += toXor
}
}
}
// KeyStream sets dst to the raw keystream.
func (c *Cipher) KeyStream(dst []byte) {
for remaining := len(dst); remaining > 0; {
// Process multiple blocks at once.
if c.off == BlockSize {
nrBlocks := remaining / BlockSize
directBytes := nrBlocks * BlockSize
if nrBlocks > 0 {
blocksFn(&c.state, nil, dst, nrBlocks, c.ietf)
remaining -= directBytes
if remaining == 0 {
return
}
dst = dst[directBytes:]
}
// If there's a partial block, generate 1 block of keystream into
// the internal buffer.
blocksFn(&c.state, nil, c.buf[:], 1, c.ietf)
c.off = 0
}
// Process partial blocks from the buffered keystream.
toCopy := BlockSize - c.off
if remaining < toCopy {
toCopy = remaining
}
if toCopy > 0 {
copy(dst[:toCopy], c.buf[c.off:c.off+toCopy])
dst = dst[toCopy:]
remaining -= toCopy
c.off += toCopy
}
}
}
// ReKey reinitializes the ChaCha20/XChaCha20 instance with the provided key
// and nonce.
func (c *Cipher) ReKey(key, nonce []byte) error {
if len(key) != KeySize {
return ErrInvalidKey
}
switch len(nonce) {
case NonceSize:
case INonceSize:
case XNonceSize:
var subkey [KeySize]byte
var subnonce [HNonceSize]byte
copy(subnonce[:], nonce[0:16])
HChaCha(key, &subnonce, &subkey)
key = subkey[:]
nonce = nonce[16:24]
defer func() {
for i := range subkey {
subkey[i] = 0
}
}()
default:
return ErrInvalidNonce
}
c.Reset()
c.state[0] = sigma0
c.state[1] = sigma1
c.state[2] = sigma2
c.state[3] = sigma3
c.state[4] = binary.LittleEndian.Uint32(key[0:4])
c.state[5] = binary.LittleEndian.Uint32(key[4:8])
c.state[6] = binary.LittleEndian.Uint32(key[8:12])
c.state[7] = binary.LittleEndian.Uint32(key[12:16])
c.state[8] = binary.LittleEndian.Uint32(key[16:20])
c.state[9] = binary.LittleEndian.Uint32(key[20:24])
c.state[10] = binary.LittleEndian.Uint32(key[24:28])
c.state[11] = binary.LittleEndian.Uint32(key[28:32])
c.state[12] = 0
if len(nonce) == INonceSize {
c.state[13] = binary.LittleEndian.Uint32(nonce[0:4])
c.state[14] = binary.LittleEndian.Uint32(nonce[4:8])
c.state[15] = binary.LittleEndian.Uint32(nonce[8:12])
c.ietf = true
} else {
c.state[13] = 0
c.state[14] = binary.LittleEndian.Uint32(nonce[0:4])
c.state[15] = binary.LittleEndian.Uint32(nonce[4:8])
c.ietf = false
}
c.off = BlockSize
return nil
}
// Seek sets the block counter to a given offset.
func (c *Cipher) Seek(blockCounter uint64) error {
if c.ietf {
if blockCounter > math.MaxUint32 {
return ErrInvalidCounter
}
c.state[12] = uint32(blockCounter)
} else {
c.state[12] = uint32(blockCounter)
c.state[13] = uint32(blockCounter >> 32)
}
c.off = BlockSize
return nil
}
// NewCipher returns a new ChaCha20/XChaCha20 instance.
func NewCipher(key, nonce []byte) (*Cipher, error) {
c := new(Cipher)
if err := c.ReKey(key, nonce); err != nil {
return nil, err
}
return c, nil
}
// HChaCha is the HChaCha20 hash function used to make XChaCha.
func HChaCha(key []byte, nonce *[HNonceSize]byte, out *[32]byte) {
var x [stateSize]uint32 // Last 4 slots unused, sigma hardcoded.
x[0] = binary.LittleEndian.Uint32(key[0:4])
x[1] = binary.LittleEndian.Uint32(key[4:8])
x[2] = binary.LittleEndian.Uint32(key[8:12])
x[3] = binary.LittleEndian.Uint32(key[12:16])
x[4] = binary.LittleEndian.Uint32(key[16:20])
x[5] = binary.LittleEndian.Uint32(key[20:24])
x[6] = binary.LittleEndian.Uint32(key[24:28])
x[7] = binary.LittleEndian.Uint32(key[28:32])
x[8] = binary.LittleEndian.Uint32(nonce[0:4])
x[9] = binary.LittleEndian.Uint32(nonce[4:8])
x[10] = binary.LittleEndian.Uint32(nonce[8:12])
x[11] = binary.LittleEndian.Uint32(nonce[12:16])
hChaChaRef(&x, out)
}
func init() {
switch runtime.GOARCH {
case "386", "amd64":
// Abuse unsafe to skip calling binary.LittleEndian.PutUint32
// in the critical path. This is a big boost on systems that are
// little endian and not overly picky about alignment.
useUnsafe = true
}
}
var _ cipher.Stream = (*Cipher)(nil)

View File

@ -1,95 +0,0 @@
// chacha20_amd64.go - AMD64 optimized chacha20.
//
// To the extent possible under law, Yawning Angel has waived all copyright
// and related or neighboring rights to chacha20, using the Creative
// Commons "CC0" public domain dedication. See LICENSE or
// <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
// +build amd64,!gccgo,!appengine
package chacha20
import (
"math"
)
var usingAVX2 = false
func blocksAmd64SSE2(x *uint32, inp, outp *byte, nrBlocks uint)
func blocksAmd64AVX2(x *uint32, inp, outp *byte, nrBlocks uint)
func cpuidAmd64(cpuidParams *uint32)
func xgetbv0Amd64(xcrVec *uint32)
func blocksAmd64(x *[stateSize]uint32, in []byte, out []byte, nrBlocks int, isIetf bool) {
// Probably unneeded, but stating this explicitly simplifies the assembly.
if nrBlocks == 0 {
return
}
if isIetf {
var totalBlocks uint64
totalBlocks = uint64(x[8]) + uint64(nrBlocks)
if totalBlocks > math.MaxUint32 {
panic("chacha20: Exceeded keystream per nonce limit")
}
}
if in == nil {
for i := range out {
out[i] = 0
}
in = out
}
// Pointless to call the AVX2 code for just a single block, since half of
// the output gets discarded...
if usingAVX2 && nrBlocks > 1 {
blocksAmd64AVX2(&x[0], &in[0], &out[0], uint(nrBlocks))
} else {
blocksAmd64SSE2(&x[0], &in[0], &out[0], uint(nrBlocks))
}
}
func supportsAVX2() bool {
// https://software.intel.com/en-us/articles/how-to-detect-new-instruction-support-in-the-4th-generation-intel-core-processor-family
const (
osXsaveBit = 1 << 27
avx2Bit = 1 << 5
)
// Check to see if CPUID actually supports the leaf that indicates AVX2.
// CPUID.(EAX=0H, ECX=0H) >= 7
regs := [4]uint32{0x00}
cpuidAmd64(&regs[0])
if regs[0] < 7 {
return false
}
// Check to see if the OS knows how to save/restore XMM/YMM state.
// CPUID.(EAX=01H, ECX=0H):ECX.OSXSAVE[bit 27]==1
regs = [4]uint32{0x01}
cpuidAmd64(&regs[0])
if regs[2]&osXsaveBit == 0 {
return false
}
xcrRegs := [2]uint32{}
xgetbv0Amd64(&xcrRegs[0])
if xcrRegs[0]&6 != 6 {
return false
}
// Check for AVX2 support.
// CPUID.(EAX=07H, ECX=0H):EBX.AVX2[bit 5]==1
regs = [4]uint32{0x07}
cpuidAmd64(&regs[0])
return regs[1]&avx2Bit != 0
}
func init() {
blocksFn = blocksAmd64
usingVectors = true
usingAVX2 = supportsAVX2()
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,392 +0,0 @@
// chacha20_ref.go - Reference ChaCha20.
//
// To the extent possible under law, Yawning Angel has waived all copyright
// and related or neighboring rights to chacha20, using the Creative
// Commons "CC0" public domain dedication. See LICENSE or
// <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
package chacha20
import (
"encoding/binary"
"math"
"unsafe"
)
func blocksRef(x *[stateSize]uint32, in []byte, out []byte, nrBlocks int, isIetf bool) {
if isIetf {
var totalBlocks uint64
totalBlocks = uint64(x[8]) + uint64(nrBlocks)
if totalBlocks > math.MaxUint32 {
panic("chacha20: Exceeded keystream per nonce limit")
}
}
// This routine ignores x[0]...x[4] in favor the const values since it's
// ever so slightly faster.
for n := 0; n < nrBlocks; n++ {
x0, x1, x2, x3 := sigma0, sigma1, sigma2, sigma3
x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15 := x[4], x[5], x[6], x[7], x[8], x[9], x[10], x[11], x[12], x[13], x[14], x[15]
for i := chachaRounds; i > 0; i -= 2 {
// quarterround(x, 0, 4, 8, 12)
x0 += x4
x12 ^= x0
x12 = (x12 << 16) | (x12 >> 16)
x8 += x12
x4 ^= x8
x4 = (x4 << 12) | (x4 >> 20)
x0 += x4
x12 ^= x0
x12 = (x12 << 8) | (x12 >> 24)
x8 += x12
x4 ^= x8
x4 = (x4 << 7) | (x4 >> 25)
// quarterround(x, 1, 5, 9, 13)
x1 += x5
x13 ^= x1
x13 = (x13 << 16) | (x13 >> 16)
x9 += x13
x5 ^= x9
x5 = (x5 << 12) | (x5 >> 20)
x1 += x5
x13 ^= x1
x13 = (x13 << 8) | (x13 >> 24)
x9 += x13
x5 ^= x9
x5 = (x5 << 7) | (x5 >> 25)
// quarterround(x, 2, 6, 10, 14)
x2 += x6
x14 ^= x2
x14 = (x14 << 16) | (x14 >> 16)
x10 += x14
x6 ^= x10
x6 = (x6 << 12) | (x6 >> 20)
x2 += x6
x14 ^= x2
x14 = (x14 << 8) | (x14 >> 24)
x10 += x14
x6 ^= x10
x6 = (x6 << 7) | (x6 >> 25)
// quarterround(x, 3, 7, 11, 15)
x3 += x7
x15 ^= x3
x15 = (x15 << 16) | (x15 >> 16)
x11 += x15
x7 ^= x11
x7 = (x7 << 12) | (x7 >> 20)
x3 += x7
x15 ^= x3
x15 = (x15 << 8) | (x15 >> 24)
x11 += x15
x7 ^= x11
x7 = (x7 << 7) | (x7 >> 25)
// quarterround(x, 0, 5, 10, 15)
x0 += x5
x15 ^= x0
x15 = (x15 << 16) | (x15 >> 16)
x10 += x15
x5 ^= x10
x5 = (x5 << 12) | (x5 >> 20)
x0 += x5
x15 ^= x0
x15 = (x15 << 8) | (x15 >> 24)
x10 += x15
x5 ^= x10
x5 = (x5 << 7) | (x5 >> 25)
// quarterround(x, 1, 6, 11, 12)
x1 += x6
x12 ^= x1
x12 = (x12 << 16) | (x12 >> 16)
x11 += x12
x6 ^= x11
x6 = (x6 << 12) | (x6 >> 20)
x1 += x6
x12 ^= x1
x12 = (x12 << 8) | (x12 >> 24)
x11 += x12
x6 ^= x11
x6 = (x6 << 7) | (x6 >> 25)
// quarterround(x, 2, 7, 8, 13)
x2 += x7
x13 ^= x2
x13 = (x13 << 16) | (x13 >> 16)
x8 += x13
x7 ^= x8
x7 = (x7 << 12) | (x7 >> 20)
x2 += x7
x13 ^= x2
x13 = (x13 << 8) | (x13 >> 24)
x8 += x13
x7 ^= x8
x7 = (x7 << 7) | (x7 >> 25)
// quarterround(x, 3, 4, 9, 14)
x3 += x4
x14 ^= x3
x14 = (x14 << 16) | (x14 >> 16)
x9 += x14
x4 ^= x9
x4 = (x4 << 12) | (x4 >> 20)
x3 += x4
x14 ^= x3
x14 = (x14 << 8) | (x14 >> 24)
x9 += x14
x4 ^= x9
x4 = (x4 << 7) | (x4 >> 25)
}
// On amd64 at least, this is a rather big boost.
if useUnsafe {
if in != nil {
inArr := (*[16]uint32)(unsafe.Pointer(&in[n*BlockSize]))
outArr := (*[16]uint32)(unsafe.Pointer(&out[n*BlockSize]))
outArr[0] = inArr[0] ^ (x0 + sigma0)
outArr[1] = inArr[1] ^ (x1 + sigma1)
outArr[2] = inArr[2] ^ (x2 + sigma2)
outArr[3] = inArr[3] ^ (x3 + sigma3)
outArr[4] = inArr[4] ^ (x4 + x[4])
outArr[5] = inArr[5] ^ (x5 + x[5])
outArr[6] = inArr[6] ^ (x6 + x[6])
outArr[7] = inArr[7] ^ (x7 + x[7])
outArr[8] = inArr[8] ^ (x8 + x[8])
outArr[9] = inArr[9] ^ (x9 + x[9])
outArr[10] = inArr[10] ^ (x10 + x[10])
outArr[11] = inArr[11] ^ (x11 + x[11])
outArr[12] = inArr[12] ^ (x12 + x[12])
outArr[13] = inArr[13] ^ (x13 + x[13])
outArr[14] = inArr[14] ^ (x14 + x[14])
outArr[15] = inArr[15] ^ (x15 + x[15])
} else {
outArr := (*[16]uint32)(unsafe.Pointer(&out[n*BlockSize]))
outArr[0] = x0 + sigma0
outArr[1] = x1 + sigma1
outArr[2] = x2 + sigma2
outArr[3] = x3 + sigma3
outArr[4] = x4 + x[4]
outArr[5] = x5 + x[5]
outArr[6] = x6 + x[6]
outArr[7] = x7 + x[7]
outArr[8] = x8 + x[8]
outArr[9] = x9 + x[9]
outArr[10] = x10 + x[10]
outArr[11] = x11 + x[11]
outArr[12] = x12 + x[12]
outArr[13] = x13 + x[13]
outArr[14] = x14 + x[14]
outArr[15] = x15 + x[15]
}
} else {
// Slow path, either the architecture cares about alignment, or is not little endian.
x0 += sigma0
x1 += sigma1
x2 += sigma2
x3 += sigma3
x4 += x[4]
x5 += x[5]
x6 += x[6]
x7 += x[7]
x8 += x[8]
x9 += x[9]
x10 += x[10]
x11 += x[11]
x12 += x[12]
x13 += x[13]
x14 += x[14]
x15 += x[15]
if in != nil {
binary.LittleEndian.PutUint32(out[0:4], binary.LittleEndian.Uint32(in[0:4])^x0)
binary.LittleEndian.PutUint32(out[4:8], binary.LittleEndian.Uint32(in[4:8])^x1)
binary.LittleEndian.PutUint32(out[8:12], binary.LittleEndian.Uint32(in[8:12])^x2)
binary.LittleEndian.PutUint32(out[12:16], binary.LittleEndian.Uint32(in[12:16])^x3)
binary.LittleEndian.PutUint32(out[16:20], binary.LittleEndian.Uint32(in[16:20])^x4)
binary.LittleEndian.PutUint32(out[20:24], binary.LittleEndian.Uint32(in[20:24])^x5)
binary.LittleEndian.PutUint32(out[24:28], binary.LittleEndian.Uint32(in[24:28])^x6)
binary.LittleEndian.PutUint32(out[28:32], binary.LittleEndian.Uint32(in[28:32])^x7)
binary.LittleEndian.PutUint32(out[32:36], binary.LittleEndian.Uint32(in[32:36])^x8)
binary.LittleEndian.PutUint32(out[36:40], binary.LittleEndian.Uint32(in[36:40])^x9)
binary.LittleEndian.PutUint32(out[40:44], binary.LittleEndian.Uint32(in[40:44])^x10)
binary.LittleEndian.PutUint32(out[44:48], binary.LittleEndian.Uint32(in[44:48])^x11)
binary.LittleEndian.PutUint32(out[48:52], binary.LittleEndian.Uint32(in[48:52])^x12)
binary.LittleEndian.PutUint32(out[52:56], binary.LittleEndian.Uint32(in[52:56])^x13)
binary.LittleEndian.PutUint32(out[56:60], binary.LittleEndian.Uint32(in[56:60])^x14)
binary.LittleEndian.PutUint32(out[60:64], binary.LittleEndian.Uint32(in[60:64])^x15)
in = in[BlockSize:]
} else {
binary.LittleEndian.PutUint32(out[0:4], x0)
binary.LittleEndian.PutUint32(out[4:8], x1)
binary.LittleEndian.PutUint32(out[8:12], x2)
binary.LittleEndian.PutUint32(out[12:16], x3)
binary.LittleEndian.PutUint32(out[16:20], x4)
binary.LittleEndian.PutUint32(out[20:24], x5)
binary.LittleEndian.PutUint32(out[24:28], x6)
binary.LittleEndian.PutUint32(out[28:32], x7)
binary.LittleEndian.PutUint32(out[32:36], x8)
binary.LittleEndian.PutUint32(out[36:40], x9)
binary.LittleEndian.PutUint32(out[40:44], x10)
binary.LittleEndian.PutUint32(out[44:48], x11)
binary.LittleEndian.PutUint32(out[48:52], x12)
binary.LittleEndian.PutUint32(out[52:56], x13)
binary.LittleEndian.PutUint32(out[56:60], x14)
binary.LittleEndian.PutUint32(out[60:64], x15)
}
out = out[BlockSize:]
}
// Stoping at 2^70 bytes per nonce is the user's responsibility.
ctr := uint64(x[13])<<32 | uint64(x[12])
ctr++
x[12] = uint32(ctr)
x[13] = uint32(ctr >> 32)
}
}
func hChaChaRef(x *[stateSize]uint32, out *[32]byte) {
x0, x1, x2, x3 := sigma0, sigma1, sigma2, sigma3
x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15 := x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7], x[8], x[9], x[10], x[11]
for i := chachaRounds; i > 0; i -= 2 {
// quarterround(x, 0, 4, 8, 12)
x0 += x4
x12 ^= x0
x12 = (x12 << 16) | (x12 >> 16)
x8 += x12
x4 ^= x8
x4 = (x4 << 12) | (x4 >> 20)
x0 += x4
x12 ^= x0
x12 = (x12 << 8) | (x12 >> 24)
x8 += x12
x4 ^= x8
x4 = (x4 << 7) | (x4 >> 25)
// quarterround(x, 1, 5, 9, 13)
x1 += x5
x13 ^= x1
x13 = (x13 << 16) | (x13 >> 16)
x9 += x13
x5 ^= x9
x5 = (x5 << 12) | (x5 >> 20)
x1 += x5
x13 ^= x1
x13 = (x13 << 8) | (x13 >> 24)
x9 += x13
x5 ^= x9
x5 = (x5 << 7) | (x5 >> 25)
// quarterround(x, 2, 6, 10, 14)
x2 += x6
x14 ^= x2
x14 = (x14 << 16) | (x14 >> 16)
x10 += x14
x6 ^= x10
x6 = (x6 << 12) | (x6 >> 20)
x2 += x6
x14 ^= x2
x14 = (x14 << 8) | (x14 >> 24)
x10 += x14
x6 ^= x10
x6 = (x6 << 7) | (x6 >> 25)
// quarterround(x, 3, 7, 11, 15)
x3 += x7
x15 ^= x3
x15 = (x15 << 16) | (x15 >> 16)
x11 += x15
x7 ^= x11
x7 = (x7 << 12) | (x7 >> 20)
x3 += x7
x15 ^= x3
x15 = (x15 << 8) | (x15 >> 24)
x11 += x15
x7 ^= x11
x7 = (x7 << 7) | (x7 >> 25)
// quarterround(x, 0, 5, 10, 15)
x0 += x5
x15 ^= x0
x15 = (x15 << 16) | (x15 >> 16)
x10 += x15
x5 ^= x10
x5 = (x5 << 12) | (x5 >> 20)
x0 += x5
x15 ^= x0
x15 = (x15 << 8) | (x15 >> 24)
x10 += x15
x5 ^= x10
x5 = (x5 << 7) | (x5 >> 25)
// quarterround(x, 1, 6, 11, 12)
x1 += x6
x12 ^= x1
x12 = (x12 << 16) | (x12 >> 16)
x11 += x12
x6 ^= x11
x6 = (x6 << 12) | (x6 >> 20)
x1 += x6
x12 ^= x1
x12 = (x12 << 8) | (x12 >> 24)
x11 += x12
x6 ^= x11
x6 = (x6 << 7) | (x6 >> 25)
// quarterround(x, 2, 7, 8, 13)
x2 += x7
x13 ^= x2
x13 = (x13 << 16) | (x13 >> 16)
x8 += x13
x7 ^= x8
x7 = (x7 << 12) | (x7 >> 20)
x2 += x7
x13 ^= x2
x13 = (x13 << 8) | (x13 >> 24)
x8 += x13
x7 ^= x8
x7 = (x7 << 7) | (x7 >> 25)
// quarterround(x, 3, 4, 9, 14)
x3 += x4
x14 ^= x3
x14 = (x14 << 16) | (x14 >> 16)
x9 += x14
x4 ^= x9
x4 = (x4 << 12) | (x4 >> 20)
x3 += x4
x14 ^= x3
x14 = (x14 << 8) | (x14 >> 24)
x9 += x14
x4 ^= x9
x4 = (x4 << 7) | (x4 >> 25)
}
// HChaCha returns x0...x3 | x12...x15, which corresponds to the
// indexes of the ChaCha constant and the indexes of the IV.
if useUnsafe {
outArr := (*[16]uint32)(unsafe.Pointer(&out[0]))
outArr[0] = x0
outArr[1] = x1
outArr[2] = x2
outArr[3] = x3
outArr[4] = x12
outArr[5] = x13
outArr[6] = x14
outArr[7] = x15
} else {
binary.LittleEndian.PutUint32(out[0:4], x0)
binary.LittleEndian.PutUint32(out[4:8], x1)
binary.LittleEndian.PutUint32(out[8:12], x2)
binary.LittleEndian.PutUint32(out[12:16], x3)
binary.LittleEndian.PutUint32(out[16:20], x12)
binary.LittleEndian.PutUint32(out[20:24], x13)
binary.LittleEndian.PutUint32(out[24:28], x14)
binary.LittleEndian.PutUint32(out[28:32], x15)
}
return
}

View File

@ -1,27 +0,0 @@
Copyright (c) 2012 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,340 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package extra25519
import (
"crypto/sha512"
"github.com/agl/ed25519/edwards25519"
)
// PrivateKeyToCurve25519 converts an ed25519 private key into a corresponding
// curve25519 private key such that the resulting curve25519 public key will
// equal the result from PublicKeyToCurve25519.
func PrivateKeyToCurve25519(curve25519Private *[32]byte, privateKey *[64]byte) {
h := sha512.New()
h.Write(privateKey[:32])
digest := h.Sum(nil)
digest[0] &= 248
digest[31] &= 127
digest[31] |= 64
copy(curve25519Private[:], digest)
}
func edwardsToMontgomeryX(outX, y *edwards25519.FieldElement) {
// We only need the x-coordinate of the curve25519 point, which I'll
// call u. The isomorphism is u=(y+1)/(1-y), since y=Y/Z, this gives
// u=(Y+Z)/(Z-Y). We know that Z=1, thus u=(Y+1)/(1-Y).
var oneMinusY edwards25519.FieldElement
edwards25519.FeOne(&oneMinusY)
edwards25519.FeSub(&oneMinusY, &oneMinusY, y)
edwards25519.FeInvert(&oneMinusY, &oneMinusY)
edwards25519.FeOne(outX)
edwards25519.FeAdd(outX, outX, y)
edwards25519.FeMul(outX, outX, &oneMinusY)
}
// PublicKeyToCurve25519 converts an Ed25519 public key into the curve25519
// public key that would be generated from the same private key.
func PublicKeyToCurve25519(curve25519Public *[32]byte, publicKey *[32]byte) bool {
var A edwards25519.ExtendedGroupElement
if !A.FromBytes(publicKey) {
return false
}
// A.Z = 1 as a postcondition of FromBytes.
var x edwards25519.FieldElement
edwardsToMontgomeryX(&x, &A.Y)
edwards25519.FeToBytes(curve25519Public, &x)
return true
}
// sqrtMinusAPlus2 is sqrt(-(486662+2))
var sqrtMinusAPlus2 = edwards25519.FieldElement{
-12222970, -8312128, -11511410, 9067497, -15300785, -241793, 25456130, 14121551, -12187136, 3972024,
}
// sqrtMinusHalf is sqrt(-1/2)
var sqrtMinusHalf = edwards25519.FieldElement{
-17256545, 3971863, 28865457, -1750208, 27359696, -16640980, 12573105, 1002827, -163343, 11073975,
}
// halfQMinus1Bytes is (2^255-20)/2 expressed in little endian form.
var halfQMinus1Bytes = [32]byte{
0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f,
}
// feBytesLess returns one if a <= b and zero otherwise.
func feBytesLE(a, b *[32]byte) int32 {
equalSoFar := int32(-1)
greater := int32(0)
for i := uint(31); i < 32; i-- {
x := int32(a[i])
y := int32(b[i])
greater = (^equalSoFar & greater) | (equalSoFar & ((x - y) >> 31))
equalSoFar = equalSoFar & (((x ^ y) - 1) >> 31)
}
return int32(^equalSoFar & 1 & greater)
}
// ScalarBaseMult computes a curve25519 public key from a private key and also
// a uniform representative for that public key. Note that this function will
// fail and return false for about half of private keys.
// See http://elligator.cr.yp.to/elligator-20130828.pdf.
func ScalarBaseMult(publicKey, representative, privateKey *[32]byte) bool {
var maskedPrivateKey [32]byte
copy(maskedPrivateKey[:], privateKey[:])
maskedPrivateKey[0] &= 248
maskedPrivateKey[31] &= 127
maskedPrivateKey[31] |= 64
var A edwards25519.ExtendedGroupElement
edwards25519.GeScalarMultBase(&A, &maskedPrivateKey)
var inv1 edwards25519.FieldElement
edwards25519.FeSub(&inv1, &A.Z, &A.Y)
edwards25519.FeMul(&inv1, &inv1, &A.X)
edwards25519.FeInvert(&inv1, &inv1)
var t0, u edwards25519.FieldElement
edwards25519.FeMul(&u, &inv1, &A.X)
edwards25519.FeAdd(&t0, &A.Y, &A.Z)
edwards25519.FeMul(&u, &u, &t0)
var v edwards25519.FieldElement
edwards25519.FeMul(&v, &t0, &inv1)
edwards25519.FeMul(&v, &v, &A.Z)
edwards25519.FeMul(&v, &v, &sqrtMinusAPlus2)
var b edwards25519.FieldElement
edwards25519.FeAdd(&b, &u, &edwards25519.A)
var c, b3, b7, b8 edwards25519.FieldElement
edwards25519.FeSquare(&b3, &b) // 2
edwards25519.FeMul(&b3, &b3, &b) // 3
edwards25519.FeSquare(&c, &b3) // 6
edwards25519.FeMul(&b7, &c, &b) // 7
edwards25519.FeMul(&b8, &b7, &b) // 8
edwards25519.FeMul(&c, &b7, &u)
q58(&c, &c)
var chi edwards25519.FieldElement
edwards25519.FeSquare(&chi, &c)
edwards25519.FeSquare(&chi, &chi)
edwards25519.FeSquare(&t0, &u)
edwards25519.FeMul(&chi, &chi, &t0)
edwards25519.FeSquare(&t0, &b7) // 14
edwards25519.FeMul(&chi, &chi, &t0)
edwards25519.FeNeg(&chi, &chi)
var chiBytes [32]byte
edwards25519.FeToBytes(&chiBytes, &chi)
// chi[1] is either 0 or 0xff
if chiBytes[1] == 0xff {
return false
}
// Calculate r1 = sqrt(-u/(2*(u+A)))
var r1 edwards25519.FieldElement
edwards25519.FeMul(&r1, &c, &u)
edwards25519.FeMul(&r1, &r1, &b3)
edwards25519.FeMul(&r1, &r1, &sqrtMinusHalf)
var maybeSqrtM1 edwards25519.FieldElement
edwards25519.FeSquare(&t0, &r1)
edwards25519.FeMul(&t0, &t0, &b)
edwards25519.FeAdd(&t0, &t0, &t0)
edwards25519.FeAdd(&t0, &t0, &u)
edwards25519.FeOne(&maybeSqrtM1)
edwards25519.FeCMove(&maybeSqrtM1, &edwards25519.SqrtM1, edwards25519.FeIsNonZero(&t0))
edwards25519.FeMul(&r1, &r1, &maybeSqrtM1)
// Calculate r = sqrt(-(u+A)/(2u))
var r edwards25519.FieldElement
edwards25519.FeSquare(&t0, &c) // 2
edwards25519.FeMul(&t0, &t0, &c) // 3
edwards25519.FeSquare(&t0, &t0) // 6
edwards25519.FeMul(&r, &t0, &c) // 7
edwards25519.FeSquare(&t0, &u) // 2
edwards25519.FeMul(&t0, &t0, &u) // 3
edwards25519.FeMul(&r, &r, &t0)
edwards25519.FeSquare(&t0, &b8) // 16
edwards25519.FeMul(&t0, &t0, &b8) // 24
edwards25519.FeMul(&t0, &t0, &b) // 25
edwards25519.FeMul(&r, &r, &t0)
edwards25519.FeMul(&r, &r, &sqrtMinusHalf)
edwards25519.FeSquare(&t0, &r)
edwards25519.FeMul(&t0, &t0, &u)
edwards25519.FeAdd(&t0, &t0, &t0)
edwards25519.FeAdd(&t0, &t0, &b)
edwards25519.FeOne(&maybeSqrtM1)
edwards25519.FeCMove(&maybeSqrtM1, &edwards25519.SqrtM1, edwards25519.FeIsNonZero(&t0))
edwards25519.FeMul(&r, &r, &maybeSqrtM1)
var vBytes [32]byte
edwards25519.FeToBytes(&vBytes, &v)
vInSquareRootImage := feBytesLE(&vBytes, &halfQMinus1Bytes)
edwards25519.FeCMove(&r, &r1, vInSquareRootImage)
edwards25519.FeToBytes(publicKey, &u)
edwards25519.FeToBytes(representative, &r)
return true
}
// q58 calculates out = z^((p-5)/8).
func q58(out, z *edwards25519.FieldElement) {
var t1, t2, t3 edwards25519.FieldElement
var i int
edwards25519.FeSquare(&t1, z) // 2^1
edwards25519.FeMul(&t1, &t1, z) // 2^1 + 2^0
edwards25519.FeSquare(&t1, &t1) // 2^2 + 2^1
edwards25519.FeSquare(&t2, &t1) // 2^3 + 2^2
edwards25519.FeSquare(&t2, &t2) // 2^4 + 2^3
edwards25519.FeMul(&t2, &t2, &t1) // 4,3,2,1
edwards25519.FeMul(&t1, &t2, z) // 4..0
edwards25519.FeSquare(&t2, &t1) // 5..1
for i = 1; i < 5; i++ { // 9,8,7,6,5
edwards25519.FeSquare(&t2, &t2)
}
edwards25519.FeMul(&t1, &t2, &t1) // 9,8,7,6,5,4,3,2,1,0
edwards25519.FeSquare(&t2, &t1) // 10..1
for i = 1; i < 10; i++ { // 19..10
edwards25519.FeSquare(&t2, &t2)
}
edwards25519.FeMul(&t2, &t2, &t1) // 19..0
edwards25519.FeSquare(&t3, &t2) // 20..1
for i = 1; i < 20; i++ { // 39..20
edwards25519.FeSquare(&t3, &t3)
}
edwards25519.FeMul(&t2, &t3, &t2) // 39..0
edwards25519.FeSquare(&t2, &t2) // 40..1
for i = 1; i < 10; i++ { // 49..10
edwards25519.FeSquare(&t2, &t2)
}
edwards25519.FeMul(&t1, &t2, &t1) // 49..0
edwards25519.FeSquare(&t2, &t1) // 50..1
for i = 1; i < 50; i++ { // 99..50
edwards25519.FeSquare(&t2, &t2)
}
edwards25519.FeMul(&t2, &t2, &t1) // 99..0
edwards25519.FeSquare(&t3, &t2) // 100..1
for i = 1; i < 100; i++ { // 199..100
edwards25519.FeSquare(&t3, &t3)
}
edwards25519.FeMul(&t2, &t3, &t2) // 199..0
edwards25519.FeSquare(&t2, &t2) // 200..1
for i = 1; i < 50; i++ { // 249..50
edwards25519.FeSquare(&t2, &t2)
}
edwards25519.FeMul(&t1, &t2, &t1) // 249..0
edwards25519.FeSquare(&t1, &t1) // 250..1
edwards25519.FeSquare(&t1, &t1) // 251..2
edwards25519.FeMul(out, &t1, z) // 251..2,0
}
// chi calculates out = z^((p-1)/2). The result is either 1, 0, or -1 depending
// on whether z is a non-zero square, zero, or a non-square.
func chi(out, z *edwards25519.FieldElement) {
var t0, t1, t2, t3 edwards25519.FieldElement
var i int
edwards25519.FeSquare(&t0, z) // 2^1
edwards25519.FeMul(&t1, &t0, z) // 2^1 + 2^0
edwards25519.FeSquare(&t0, &t1) // 2^2 + 2^1
edwards25519.FeSquare(&t2, &t0) // 2^3 + 2^2
edwards25519.FeSquare(&t2, &t2) // 4,3
edwards25519.FeMul(&t2, &t2, &t0) // 4,3,2,1
edwards25519.FeMul(&t1, &t2, z) // 4..0
edwards25519.FeSquare(&t2, &t1) // 5..1
for i = 1; i < 5; i++ { // 9,8,7,6,5
edwards25519.FeSquare(&t2, &t2)
}
edwards25519.FeMul(&t1, &t2, &t1) // 9,8,7,6,5,4,3,2,1,0
edwards25519.FeSquare(&t2, &t1) // 10..1
for i = 1; i < 10; i++ { // 19..10
edwards25519.FeSquare(&t2, &t2)
}
edwards25519.FeMul(&t2, &t2, &t1) // 19..0
edwards25519.FeSquare(&t3, &t2) // 20..1
for i = 1; i < 20; i++ { // 39..20
edwards25519.FeSquare(&t3, &t3)
}
edwards25519.FeMul(&t2, &t3, &t2) // 39..0
edwards25519.FeSquare(&t2, &t2) // 40..1
for i = 1; i < 10; i++ { // 49..10
edwards25519.FeSquare(&t2, &t2)
}
edwards25519.FeMul(&t1, &t2, &t1) // 49..0
edwards25519.FeSquare(&t2, &t1) // 50..1
for i = 1; i < 50; i++ { // 99..50
edwards25519.FeSquare(&t2, &t2)
}
edwards25519.FeMul(&t2, &t2, &t1) // 99..0
edwards25519.FeSquare(&t3, &t2) // 100..1
for i = 1; i < 100; i++ { // 199..100
edwards25519.FeSquare(&t3, &t3)
}
edwards25519.FeMul(&t2, &t3, &t2) // 199..0
edwards25519.FeSquare(&t2, &t2) // 200..1
for i = 1; i < 50; i++ { // 249..50
edwards25519.FeSquare(&t2, &t2)
}
edwards25519.FeMul(&t1, &t2, &t1) // 249..0
edwards25519.FeSquare(&t1, &t1) // 250..1
for i = 1; i < 4; i++ { // 253..4
edwards25519.FeSquare(&t1, &t1)
}
edwards25519.FeMul(out, &t1, &t0) // 253..4,2,1
}
// RepresentativeToPublicKey converts a uniform representative value for a
// curve25519 public key, as produced by ScalarBaseMult, to a curve25519 public
// key.
func RepresentativeToPublicKey(publicKey, representative *[32]byte) {
var rr2, v, e edwards25519.FieldElement
edwards25519.FeFromBytes(&rr2, representative)
edwards25519.FeSquare2(&rr2, &rr2)
rr2[0]++
edwards25519.FeInvert(&rr2, &rr2)
edwards25519.FeMul(&v, &edwards25519.A, &rr2)
edwards25519.FeNeg(&v, &v)
var v2, v3 edwards25519.FieldElement
edwards25519.FeSquare(&v2, &v)
edwards25519.FeMul(&v3, &v, &v2)
edwards25519.FeAdd(&e, &v3, &v)
edwards25519.FeMul(&v2, &v2, &edwards25519.A)
edwards25519.FeAdd(&e, &v2, &e)
chi(&e, &e)
var eBytes [32]byte
edwards25519.FeToBytes(&eBytes, &e)
// eBytes[1] is either 0 (for e = 1) or 0xff (for e = -1)
eIsMinus1 := int32(eBytes[1]) & 1
var negV edwards25519.FieldElement
edwards25519.FeNeg(&negV, &v)
edwards25519.FeCMove(&v, &negV, eIsMinus1)
edwards25519.FeZero(&v2)
edwards25519.FeCMove(&v2, &edwards25519.A, eIsMinus1)
edwards25519.FeSub(&v, &v, &v2)
edwards25519.FeToBytes(publicKey, &v)
}

View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2016 Richard Barnes
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,88 +0,0 @@
![A lock with a mint leaf](https://ipv.sx/mint/mint.svg)
mint - A Minimal TLS 1.3 stack
==============================
[![Build Status](https://circleci.com/gh/bifurcation/mint.svg)](https://circleci.com/gh/bifurcation/mint)
This project is primarily a learning effort for me to understand the [TLS
1.3](http://tlswg.github.io/tls13-spec/) protocol. The goal is to arrive at a
pretty complete implementation of TLS 1.3, with minimal, elegant code that
demonstrates how things work. Testing is a priority to ensure correctness, but
otherwise, the quality of the software engineering might not be at a level where
it makes sense to integrate this with other libraries. Backward compatibility
is not an objective.
We borrow liberally from the [Go TLS
library](https://golang.org/pkg/crypto/tls/), especially where TLS 1.3 aligns
with earlier TLS versions. However, unnecessary parts will be ruthlessly cut
off.
## Quickstart
Installation is the same as for any other Go package:
```
go get github.com/bifurcation/mint
```
The API is pretty much the same as for the TLS module, with `Dial` and `Listen`
methods wrapping the underlying socket APIs.
```
conn, err := mint.Dial("tcp", "localhost:4430", &mint.Config{...})
...
listener, err := mint.Listen("tcp", "localhost:4430", &mint.Config{...})
```
Documentation is available on
[godoc.org](https://godoc.org/github.com/bifurcation/mint)
## Interoperability testing
The `mint-client` and `mint-server` executables are included to make it easy to
do basic interoperability tests with other TLS 1.3 implementations. The steps
for testing against NSS are as follows.
```
# Install mint
go get github.com/bifurcation/mint
# Environment for NSS (you'll probably want a new directory)
NSS_ROOT=<whereever you want to put NSS>
mkdir $NSS_ROOT
cd $NSS_ROOT
export USE_64=1
export ENABLE_TLS_1_3=1
export HOST=localhost
export DOMSUF=localhost
# Build NSS
hg clone https://hg.mozilla.org/projects/nss
hg clone https://hg.mozilla.org/projects/nspr
cd nss
make nss_build_all
export PLATFORM=`cat $NSS_ROOT/dist/latest`
export DYLD_LIBRARY_PATH=$NSS_ROOT/dist/$PLATFORM/lib
export LD_LIBRARY_PATH=$NSS_ROOT/dist/$PLATFORM/lib
# Run NSS tests (this creates data for the server to use)
cd tests/ssl_gtests
./ssl_gtests.sh
# Test with client=mint server=NSS
cd $NSS_ROOT
./dist/$PLATFORM/bin/selfserv -d tests_results/security/$HOST.1/ssl_gtests/ -n rsa -p 4430
# if you get `NSS_Init failed.`, check the path above, particularly around $HOST
# ...
go run $GOPATH/src/github.com/bifurcation/mint/bin/mint-client/main.go
# Test with client=NSS server=mint
go run $GOPATH/src/github.com/bifurcation/mint/bin/mint-server/main.go
# ...
cd $NSS_ROOT
dist/$PLATFORM/bin/tstclnt -d tests_results/security/$HOST/ssl_gtests/ -V tls1.3:tls1.3 -h 127.0.0.1 -p 4430 -o
```

View File

@ -1,99 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package mint
import "strconv"
type Alert uint8
const (
// alert level
AlertLevelWarning = 1
AlertLevelError = 2
)
const (
AlertCloseNotify Alert = 0
AlertUnexpectedMessage Alert = 10
AlertBadRecordMAC Alert = 20
AlertDecryptionFailed Alert = 21
AlertRecordOverflow Alert = 22
AlertDecompressionFailure Alert = 30
AlertHandshakeFailure Alert = 40
AlertBadCertificate Alert = 42
AlertUnsupportedCertificate Alert = 43
AlertCertificateRevoked Alert = 44
AlertCertificateExpired Alert = 45
AlertCertificateUnknown Alert = 46
AlertIllegalParameter Alert = 47
AlertUnknownCA Alert = 48
AlertAccessDenied Alert = 49
AlertDecodeError Alert = 50
AlertDecryptError Alert = 51
AlertProtocolVersion Alert = 70
AlertInsufficientSecurity Alert = 71
AlertInternalError Alert = 80
AlertInappropriateFallback Alert = 86
AlertUserCanceled Alert = 90
AlertNoRenegotiation Alert = 100
AlertMissingExtension Alert = 109
AlertUnsupportedExtension Alert = 110
AlertCertificateUnobtainable Alert = 111
AlertUnrecognizedName Alert = 112
AlertBadCertificateStatsResponse Alert = 113
AlertBadCertificateHashValue Alert = 114
AlertUnknownPSKIdentity Alert = 115
AlertNoApplicationProtocol Alert = 120
AlertWouldBlock Alert = 254
AlertNoAlert Alert = 255
)
var alertText = map[Alert]string{
AlertCloseNotify: "close notify",
AlertUnexpectedMessage: "unexpected message",
AlertBadRecordMAC: "bad record MAC",
AlertDecryptionFailed: "decryption failed",
AlertRecordOverflow: "record overflow",
AlertDecompressionFailure: "decompression failure",
AlertHandshakeFailure: "handshake failure",
AlertBadCertificate: "bad certificate",
AlertUnsupportedCertificate: "unsupported certificate",
AlertCertificateRevoked: "revoked certificate",
AlertCertificateExpired: "expired certificate",
AlertCertificateUnknown: "unknown certificate",
AlertIllegalParameter: "illegal parameter",
AlertUnknownCA: "unknown certificate authority",
AlertAccessDenied: "access denied",
AlertDecodeError: "error decoding message",
AlertDecryptError: "error decrypting message",
AlertProtocolVersion: "protocol version not supported",
AlertInsufficientSecurity: "insufficient security level",
AlertInternalError: "internal error",
AlertInappropriateFallback: "inappropriate fallback",
AlertUserCanceled: "user canceled",
AlertMissingExtension: "missing extension",
AlertUnsupportedExtension: "unsupported extension",
AlertCertificateUnobtainable: "certificate unobtainable",
AlertUnrecognizedName: "unrecognized name",
AlertBadCertificateStatsResponse: "bad certificate status response",
AlertBadCertificateHashValue: "bad certificate hash value",
AlertUnknownPSKIdentity: "unknown PSK identity",
AlertNoApplicationProtocol: "no application protocol",
AlertNoRenegotiation: "no renegotiation",
AlertWouldBlock: "would have blocked",
AlertNoAlert: "no alert",
}
func (e Alert) String() string {
s, ok := alertText[e]
if ok {
return s
}
return "alert(" + strconv.Itoa(int(e)) + ")"
}
func (e Alert) Error() string {
return e.String()
}

View File

@ -1,942 +0,0 @@
package mint
import (
"bytes"
"crypto"
"hash"
"time"
)
// Client State Machine
//
// START <----+
// Send ClientHello | | Recv HelloRetryRequest
// / v |
// | WAIT_SH ---+
// Can | | Recv ServerHello
// send | V
// early | WAIT_EE
// data | | Recv EncryptedExtensions
// | +--------+--------+
// | Using | | Using certificate
// | PSK | v
// | | WAIT_CERT_CR
// | | Recv | | Recv CertificateRequest
// | | Certificate | v
// | | | WAIT_CERT
// | | | | Recv Certificate
// | | v v
// | | WAIT_CV
// | | | Recv CertificateVerify
// | +> WAIT_FINISHED <+
// | | Recv Finished
// \ |
// | [Send EndOfEarlyData]
// | [Send Certificate [+ CertificateVerify]]
// | Send Finished
// Can send v
// app data --> CONNECTED
// after
// here
//
// State Instructions
// START Send(CH); [RekeyOut; SendEarlyData]
// WAIT_SH Send(CH) || RekeyIn
// WAIT_EE {}
// WAIT_CERT_CR {}
// WAIT_CERT {}
// WAIT_CV {}
// WAIT_FINISHED RekeyIn; [Send(EOED);] RekeyOut; [SendCert; SendCV;] SendFin; RekeyOut;
// CONNECTED StoreTicket || (RekeyIn; [RekeyOut])
type ClientStateStart struct {
Caps Capabilities
Opts ConnectionOptions
Params ConnectionParameters
cookie []byte
firstClientHello *HandshakeMessage
helloRetryRequest *HandshakeMessage
}
func (state ClientStateStart) Next(hm *HandshakeMessage) (HandshakeState, []HandshakeAction, Alert) {
if hm != nil {
logf(logTypeHandshake, "[ClientStateStart] Unexpected non-nil message")
return nil, nil, AlertUnexpectedMessage
}
// key_shares
offeredDH := map[NamedGroup][]byte{}
ks := KeyShareExtension{
HandshakeType: HandshakeTypeClientHello,
Shares: make([]KeyShareEntry, len(state.Caps.Groups)),
}
for i, group := range state.Caps.Groups {
pub, priv, err := newKeyShare(group)
if err != nil {
logf(logTypeHandshake, "[ClientStateStart] Error generating key share [%v]", err)
return nil, nil, AlertInternalError
}
ks.Shares[i].Group = group
ks.Shares[i].KeyExchange = pub
offeredDH[group] = priv
}
logf(logTypeHandshake, "opts: %+v", state.Opts)
// supported_versions, supported_groups, signature_algorithms, server_name
sv := SupportedVersionsExtension{Versions: []uint16{supportedVersion}}
sni := ServerNameExtension(state.Opts.ServerName)
sg := SupportedGroupsExtension{Groups: state.Caps.Groups}
sa := SignatureAlgorithmsExtension{Algorithms: state.Caps.SignatureSchemes}
state.Params.ServerName = state.Opts.ServerName
// Application Layer Protocol Negotiation
var alpn *ALPNExtension
if (state.Opts.NextProtos != nil) && (len(state.Opts.NextProtos) > 0) {
alpn = &ALPNExtension{Protocols: state.Opts.NextProtos}
}
// Construct base ClientHello
ch := &ClientHelloBody{
CipherSuites: state.Caps.CipherSuites,
}
_, err := prng.Read(ch.Random[:])
if err != nil {
logf(logTypeHandshake, "[ClientStateStart] Error creating ClientHello random [%v]", err)
return nil, nil, AlertInternalError
}
for _, ext := range []ExtensionBody{&sv, &sni, &ks, &sg, &sa} {
err := ch.Extensions.Add(ext)
if err != nil {
logf(logTypeHandshake, "[ClientStateStart] Error adding extension type=[%v] [%v]", ext.Type(), err)
return nil, nil, AlertInternalError
}
}
// XXX: These optional extensions can't be folded into the above because Go
// interface-typed values are never reported as nil
if alpn != nil {
err := ch.Extensions.Add(alpn)
if err != nil {
logf(logTypeHandshake, "[ClientStateStart] Error adding ALPN extension [%v]", err)
return nil, nil, AlertInternalError
}
}
if state.cookie != nil {
err := ch.Extensions.Add(&CookieExtension{Cookie: state.cookie})
if err != nil {
logf(logTypeHandshake, "[ClientStateStart] Error adding ALPN extension [%v]", err)
return nil, nil, AlertInternalError
}
}
// Run the external extension handler.
if state.Caps.ExtensionHandler != nil {
err := state.Caps.ExtensionHandler.Send(HandshakeTypeClientHello, &ch.Extensions)
if err != nil {
logf(logTypeHandshake, "[ClientStateStart] Error running external extension sender [%v]", err)
return nil, nil, AlertInternalError
}
}
// Handle PSK and EarlyData just before transmitting, so that we can
// calculate the PSK binder value
var psk *PreSharedKeyExtension
var ed *EarlyDataExtension
var offeredPSK PreSharedKey
var earlyHash crypto.Hash
var earlySecret []byte
var clientEarlyTrafficKeys keySet
var clientHello *HandshakeMessage
if key, ok := state.Caps.PSKs.Get(state.Opts.ServerName); ok {
offeredPSK = key
// Narrow ciphersuites to ones that match PSK hash
params, ok := cipherSuiteMap[key.CipherSuite]
if !ok {
logf(logTypeHandshake, "[ClientStateStart] PSK for unknown ciphersuite")
return nil, nil, AlertInternalError
}
compatibleSuites := []CipherSuite{}
for _, suite := range ch.CipherSuites {
if cipherSuiteMap[suite].Hash == params.Hash {
compatibleSuites = append(compatibleSuites, suite)
}
}
ch.CipherSuites = compatibleSuites
// Signal early data if we're going to do it
if len(state.Opts.EarlyData) > 0 {
state.Params.ClientSendingEarlyData = true
ed = &EarlyDataExtension{}
err = ch.Extensions.Add(ed)
if err != nil {
logf(logTypeHandshake, "Error adding early data extension: %v", err)
return nil, nil, AlertInternalError
}
}
// Signal supported PSK key exchange modes
if len(state.Caps.PSKModes) == 0 {
logf(logTypeHandshake, "PSK selected, but no PSKModes")
return nil, nil, AlertInternalError
}
kem := &PSKKeyExchangeModesExtension{KEModes: state.Caps.PSKModes}
err = ch.Extensions.Add(kem)
if err != nil {
logf(logTypeHandshake, "Error adding PSKKeyExchangeModes extension: %v", err)
return nil, nil, AlertInternalError
}
// Add the shim PSK extension to the ClientHello
logf(logTypeHandshake, "Adding PSK extension with id = %x", key.Identity)
psk = &PreSharedKeyExtension{
HandshakeType: HandshakeTypeClientHello,
Identities: []PSKIdentity{
{
Identity: key.Identity,
ObfuscatedTicketAge: uint32(time.Since(key.ReceivedAt)/time.Millisecond) + key.TicketAgeAdd,
},
},
Binders: []PSKBinderEntry{
// Note: Stub to get the length fields right
{Binder: bytes.Repeat([]byte{0x00}, params.Hash.Size())},
},
}
ch.Extensions.Add(psk)
// Compute the binder key
h0 := params.Hash.New().Sum(nil)
zero := bytes.Repeat([]byte{0}, params.Hash.Size())
earlyHash = params.Hash
earlySecret = HkdfExtract(params.Hash, zero, key.Key)
logf(logTypeCrypto, "early secret: [%d] %x", len(earlySecret), earlySecret)
binderLabel := labelExternalBinder
if key.IsResumption {
binderLabel = labelResumptionBinder
}
binderKey := deriveSecret(params, earlySecret, binderLabel, h0)
logf(logTypeCrypto, "binder key: [%d] %x", len(binderKey), binderKey)
// Compute the binder value
trunc, err := ch.Truncated()
if err != nil {
logf(logTypeHandshake, "[ClientStateStart] Error marshaling truncated ClientHello [%v]", err)
return nil, nil, AlertInternalError
}
truncHash := params.Hash.New()
truncHash.Write(trunc)
binder := computeFinishedData(params, binderKey, truncHash.Sum(nil))
// Replace the PSK extension
psk.Binders[0].Binder = binder
ch.Extensions.Add(psk)
// If we got here, the earlier marshal succeeded (in ch.Truncated()), so
// this one should too.
clientHello, _ = HandshakeMessageFromBody(ch)
// Compute early traffic keys
h := params.Hash.New()
h.Write(clientHello.Marshal())
chHash := h.Sum(nil)
earlyTrafficSecret := deriveSecret(params, earlySecret, labelEarlyTrafficSecret, chHash)
logf(logTypeCrypto, "early traffic secret: [%d] %x", len(earlyTrafficSecret), earlyTrafficSecret)
clientEarlyTrafficKeys = makeTrafficKeys(params, earlyTrafficSecret)
} else if len(state.Opts.EarlyData) > 0 {
logf(logTypeHandshake, "[ClientStateWaitSH] Early data without PSK")
return nil, nil, AlertInternalError
} else {
clientHello, err = HandshakeMessageFromBody(ch)
if err != nil {
logf(logTypeHandshake, "[ClientStateStart] Error marshaling ClientHello [%v]", err)
return nil, nil, AlertInternalError
}
}
logf(logTypeHandshake, "[ClientStateStart] -> [ClientStateWaitSH]")
nextState := ClientStateWaitSH{
Caps: state.Caps,
Opts: state.Opts,
Params: state.Params,
OfferedDH: offeredDH,
OfferedPSK: offeredPSK,
earlySecret: earlySecret,
earlyHash: earlyHash,
firstClientHello: state.firstClientHello,
helloRetryRequest: state.helloRetryRequest,
clientHello: clientHello,
}
toSend := []HandshakeAction{
SendHandshakeMessage{clientHello},
}
if state.Params.ClientSendingEarlyData {
toSend = append(toSend, []HandshakeAction{
RekeyOut{Label: "early", KeySet: clientEarlyTrafficKeys},
SendEarlyData{},
}...)
}
return nextState, toSend, AlertNoAlert
}
type ClientStateWaitSH struct {
Caps Capabilities
Opts ConnectionOptions
Params ConnectionParameters
OfferedDH map[NamedGroup][]byte
OfferedPSK PreSharedKey
PSK []byte
earlySecret []byte
earlyHash crypto.Hash
firstClientHello *HandshakeMessage
helloRetryRequest *HandshakeMessage
clientHello *HandshakeMessage
}
func (state ClientStateWaitSH) Next(hm *HandshakeMessage) (HandshakeState, []HandshakeAction, Alert) {
if hm == nil {
logf(logTypeHandshake, "[ClientStateWaitSH] Unexpected nil message")
return nil, nil, AlertUnexpectedMessage
}
bodyGeneric, err := hm.ToBody()
if err != nil {
logf(logTypeHandshake, "[ClientStateWaitSH] Error decoding message: %v", err)
return nil, nil, AlertDecodeError
}
switch body := bodyGeneric.(type) {
case *HelloRetryRequestBody:
hrr := body
if state.helloRetryRequest != nil {
logf(logTypeHandshake, "[ClientStateWaitSH] Received a second HelloRetryRequest")
return nil, nil, AlertUnexpectedMessage
}
// Check that the version sent by the server is the one we support
if hrr.Version != supportedVersion {
logf(logTypeHandshake, "[ClientStateWaitSH] Unsupported version [%v]", hrr.Version)
return nil, nil, AlertProtocolVersion
}
// Check that the server provided a supported ciphersuite
supportedCipherSuite := false
for _, suite := range state.Caps.CipherSuites {
supportedCipherSuite = supportedCipherSuite || (suite == hrr.CipherSuite)
}
if !supportedCipherSuite {
logf(logTypeHandshake, "[ClientStateWaitSH] Unsupported ciphersuite [%04x]", hrr.CipherSuite)
return nil, nil, AlertHandshakeFailure
}
// Narrow the supported ciphersuites to the server-provided one
state.Caps.CipherSuites = []CipherSuite{hrr.CipherSuite}
// Handle external extensions.
if state.Caps.ExtensionHandler != nil {
err := state.Caps.ExtensionHandler.Receive(HandshakeTypeHelloRetryRequest, &hrr.Extensions)
if err != nil {
logf(logTypeHandshake, "[ClientWaitSH] Error running external extension handler [%v]", err)
return nil, nil, AlertInternalError
}
}
// The only thing we know how to respond to in an HRR is the Cookie
// extension, so if there is either no Cookie extension or anything other
// than a Cookie extension, we have to fail.
serverCookie := new(CookieExtension)
foundCookie := hrr.Extensions.Find(serverCookie)
if !foundCookie || len(hrr.Extensions) != 1 {
logf(logTypeHandshake, "[ClientStateWaitSH] No Cookie or extra extensions [%v] [%d]", foundCookie, len(hrr.Extensions))
return nil, nil, AlertIllegalParameter
}
// Hash the body into a pseudo-message
// XXX: Ignoring some errors here
params := cipherSuiteMap[hrr.CipherSuite]
h := params.Hash.New()
h.Write(state.clientHello.Marshal())
firstClientHello := &HandshakeMessage{
msgType: HandshakeTypeMessageHash,
body: h.Sum(nil),
}
logf(logTypeHandshake, "[ClientStateWaitSH] -> [ClientStateStart]")
return ClientStateStart{
Caps: state.Caps,
Opts: state.Opts,
cookie: serverCookie.Cookie,
firstClientHello: firstClientHello,
helloRetryRequest: hm,
}.Next(nil)
case *ServerHelloBody:
sh := body
// Check that the version sent by the server is the one we support
if sh.Version != supportedVersion {
logf(logTypeHandshake, "[ClientStateWaitSH] Unsupported version [%v]", sh.Version)
return nil, nil, AlertProtocolVersion
}
// Check that the server provided a supported ciphersuite
supportedCipherSuite := false
for _, suite := range state.Caps.CipherSuites {
supportedCipherSuite = supportedCipherSuite || (suite == sh.CipherSuite)
}
if !supportedCipherSuite {
logf(logTypeHandshake, "[ClientStateWaitSH] Unsupported ciphersuite [%04x]", sh.CipherSuite)
return nil, nil, AlertHandshakeFailure
}
// Handle external extensions.
if state.Caps.ExtensionHandler != nil {
err := state.Caps.ExtensionHandler.Receive(HandshakeTypeServerHello, &sh.Extensions)
if err != nil {
logf(logTypeHandshake, "[ClientWaitSH] Error running external extension handler [%v]", err)
return nil, nil, AlertInternalError
}
}
// Do PSK or key agreement depending on extensions
serverPSK := PreSharedKeyExtension{HandshakeType: HandshakeTypeServerHello}
serverKeyShare := KeyShareExtension{HandshakeType: HandshakeTypeServerHello}
foundPSK := sh.Extensions.Find(&serverPSK)
foundKeyShare := sh.Extensions.Find(&serverKeyShare)
if foundPSK && (serverPSK.SelectedIdentity == 0) {
state.Params.UsingPSK = true
}
var dhSecret []byte
if foundKeyShare {
sks := serverKeyShare.Shares[0]
priv, ok := state.OfferedDH[sks.Group]
if !ok {
logf(logTypeHandshake, "[ClientStateWaitSH] Key share for unknown group")
return nil, nil, AlertIllegalParameter
}
state.Params.UsingDH = true
dhSecret, _ = keyAgreement(sks.Group, sks.KeyExchange, priv)
}
suite := sh.CipherSuite
state.Params.CipherSuite = suite
params, ok := cipherSuiteMap[suite]
if !ok {
logf(logTypeCrypto, "Unsupported ciphersuite [%04x]", suite)
return nil, nil, AlertHandshakeFailure
}
// Start up the handshake hash
handshakeHash := params.Hash.New()
handshakeHash.Write(state.firstClientHello.Marshal())
handshakeHash.Write(state.helloRetryRequest.Marshal())
handshakeHash.Write(state.clientHello.Marshal())
handshakeHash.Write(hm.Marshal())
// Compute handshake secrets
zero := bytes.Repeat([]byte{0}, params.Hash.Size())
var earlySecret []byte
if state.Params.UsingPSK {
if params.Hash != state.earlyHash {
logf(logTypeCrypto, "Change of hash between early and normal init early=[%02x] suite=[%04x] hash=[%02x]",
state.earlyHash, suite, params.Hash)
}
earlySecret = state.earlySecret
} else {
earlySecret = HkdfExtract(params.Hash, zero, zero)
}
if dhSecret == nil {
dhSecret = zero
}
h0 := params.Hash.New().Sum(nil)
h2 := handshakeHash.Sum(nil)
preHandshakeSecret := deriveSecret(params, earlySecret, labelDerived, h0)
handshakeSecret := HkdfExtract(params.Hash, preHandshakeSecret, dhSecret)
clientHandshakeTrafficSecret := deriveSecret(params, handshakeSecret, labelClientHandshakeTrafficSecret, h2)
serverHandshakeTrafficSecret := deriveSecret(params, handshakeSecret, labelServerHandshakeTrafficSecret, h2)
preMasterSecret := deriveSecret(params, handshakeSecret, labelDerived, h0)
masterSecret := HkdfExtract(params.Hash, preMasterSecret, zero)
logf(logTypeCrypto, "early secret: [%d] %x", len(earlySecret), earlySecret)
logf(logTypeCrypto, "handshake secret: [%d] %x", len(handshakeSecret), handshakeSecret)
logf(logTypeCrypto, "client handshake traffic secret: [%d] %x", len(clientHandshakeTrafficSecret), clientHandshakeTrafficSecret)
logf(logTypeCrypto, "server handshake traffic secret: [%d] %x", len(serverHandshakeTrafficSecret), serverHandshakeTrafficSecret)
logf(logTypeCrypto, "master secret: [%d] %x", len(masterSecret), masterSecret)
serverHandshakeKeys := makeTrafficKeys(params, serverHandshakeTrafficSecret)
logf(logTypeHandshake, "[ClientStateWaitSH] -> [ClientStateWaitEE]")
nextState := ClientStateWaitEE{
Caps: state.Caps,
Params: state.Params,
cryptoParams: params,
handshakeHash: handshakeHash,
certificates: state.Caps.Certificates,
masterSecret: masterSecret,
clientHandshakeTrafficSecret: clientHandshakeTrafficSecret,
serverHandshakeTrafficSecret: serverHandshakeTrafficSecret,
}
toSend := []HandshakeAction{
RekeyIn{Label: "handshake", KeySet: serverHandshakeKeys},
}
return nextState, toSend, AlertNoAlert
}
logf(logTypeHandshake, "[ClientStateWaitSH] Unexpected message [%d]", hm.msgType)
return nil, nil, AlertUnexpectedMessage
}
type ClientStateWaitEE struct {
Caps Capabilities
AuthCertificate func(chain []CertificateEntry) error
Params ConnectionParameters
cryptoParams CipherSuiteParams
handshakeHash hash.Hash
certificates []*Certificate
masterSecret []byte
clientHandshakeTrafficSecret []byte
serverHandshakeTrafficSecret []byte
}
func (state ClientStateWaitEE) Next(hm *HandshakeMessage) (HandshakeState, []HandshakeAction, Alert) {
if hm == nil || hm.msgType != HandshakeTypeEncryptedExtensions {
logf(logTypeHandshake, "[ClientStateWaitEE] Unexpected message")
return nil, nil, AlertUnexpectedMessage
}
ee := EncryptedExtensionsBody{}
_, err := ee.Unmarshal(hm.body)
if err != nil {
logf(logTypeHandshake, "[ClientStateWaitEE] Error decoding message: %v", err)
return nil, nil, AlertDecodeError
}
// Handle external extensions.
if state.Caps.ExtensionHandler != nil {
err := state.Caps.ExtensionHandler.Receive(HandshakeTypeEncryptedExtensions, &ee.Extensions)
if err != nil {
logf(logTypeHandshake, "[ClientWaitStateEE] Error running external extension handler [%v]", err)
return nil, nil, AlertInternalError
}
}
serverALPN := ALPNExtension{}
serverEarlyData := EarlyDataExtension{}
gotALPN := ee.Extensions.Find(&serverALPN)
state.Params.UsingEarlyData = ee.Extensions.Find(&serverEarlyData)
if gotALPN && len(serverALPN.Protocols) > 0 {
state.Params.NextProto = serverALPN.Protocols[0]
}
state.handshakeHash.Write(hm.Marshal())
if state.Params.UsingPSK {
logf(logTypeHandshake, "[ClientStateWaitEE] -> [ClientStateWaitFinished]")
nextState := ClientStateWaitFinished{
Params: state.Params,
cryptoParams: state.cryptoParams,
handshakeHash: state.handshakeHash,
certificates: state.certificates,
masterSecret: state.masterSecret,
clientHandshakeTrafficSecret: state.clientHandshakeTrafficSecret,
serverHandshakeTrafficSecret: state.serverHandshakeTrafficSecret,
}
return nextState, nil, AlertNoAlert
}
logf(logTypeHandshake, "[ClientStateWaitEE] -> [ClientStateWaitCertCR]")
nextState := ClientStateWaitCertCR{
AuthCertificate: state.AuthCertificate,
Params: state.Params,
cryptoParams: state.cryptoParams,
handshakeHash: state.handshakeHash,
certificates: state.certificates,
masterSecret: state.masterSecret,
clientHandshakeTrafficSecret: state.clientHandshakeTrafficSecret,
serverHandshakeTrafficSecret: state.serverHandshakeTrafficSecret,
}
return nextState, nil, AlertNoAlert
}
type ClientStateWaitCertCR struct {
AuthCertificate func(chain []CertificateEntry) error
Params ConnectionParameters
cryptoParams CipherSuiteParams
handshakeHash hash.Hash
certificates []*Certificate
masterSecret []byte
clientHandshakeTrafficSecret []byte
serverHandshakeTrafficSecret []byte
}
func (state ClientStateWaitCertCR) Next(hm *HandshakeMessage) (HandshakeState, []HandshakeAction, Alert) {
if hm == nil {
logf(logTypeHandshake, "[ClientStateWaitCertCR] Unexpected message")
return nil, nil, AlertUnexpectedMessage
}
bodyGeneric, err := hm.ToBody()
if err != nil {
logf(logTypeHandshake, "[ClientStateWaitCertCR] Error decoding message: %v", err)
return nil, nil, AlertDecodeError
}
state.handshakeHash.Write(hm.Marshal())
switch body := bodyGeneric.(type) {
case *CertificateBody:
logf(logTypeHandshake, "[ClientStateWaitCertCR] -> [ClientStateWaitCV]")
nextState := ClientStateWaitCV{
AuthCertificate: state.AuthCertificate,
Params: state.Params,
cryptoParams: state.cryptoParams,
handshakeHash: state.handshakeHash,
certificates: state.certificates,
serverCertificate: body,
masterSecret: state.masterSecret,
clientHandshakeTrafficSecret: state.clientHandshakeTrafficSecret,
serverHandshakeTrafficSecret: state.serverHandshakeTrafficSecret,
}
return nextState, nil, AlertNoAlert
case *CertificateRequestBody:
// A certificate request in the handshake should have a zero-length context
if len(body.CertificateRequestContext) > 0 {
logf(logTypeHandshake, "[ClientStateWaitCertCR] Certificate request with non-empty context: %v", err)
return nil, nil, AlertIllegalParameter
}
state.Params.UsingClientAuth = true
logf(logTypeHandshake, "[ClientStateWaitCertCR] -> [ClientStateWaitCert]")
nextState := ClientStateWaitCert{
AuthCertificate: state.AuthCertificate,
Params: state.Params,
cryptoParams: state.cryptoParams,
handshakeHash: state.handshakeHash,
certificates: state.certificates,
serverCertificateRequest: body,
masterSecret: state.masterSecret,
clientHandshakeTrafficSecret: state.clientHandshakeTrafficSecret,
serverHandshakeTrafficSecret: state.serverHandshakeTrafficSecret,
}
return nextState, nil, AlertNoAlert
}
return nil, nil, AlertUnexpectedMessage
}
type ClientStateWaitCert struct {
AuthCertificate func(chain []CertificateEntry) error
Params ConnectionParameters
cryptoParams CipherSuiteParams
handshakeHash hash.Hash
certificates []*Certificate
serverCertificateRequest *CertificateRequestBody
masterSecret []byte
clientHandshakeTrafficSecret []byte
serverHandshakeTrafficSecret []byte
}
func (state ClientStateWaitCert) Next(hm *HandshakeMessage) (HandshakeState, []HandshakeAction, Alert) {
if hm == nil || hm.msgType != HandshakeTypeCertificate {
logf(logTypeHandshake, "[ClientStateWaitCert] Unexpected message")
return nil, nil, AlertUnexpectedMessage
}
cert := &CertificateBody{}
_, err := cert.Unmarshal(hm.body)
if err != nil {
logf(logTypeHandshake, "[ClientStateWaitCert] Error decoding message: %v", err)
return nil, nil, AlertDecodeError
}
state.handshakeHash.Write(hm.Marshal())
logf(logTypeHandshake, "[ClientStateWaitCert] -> [ClientStateWaitCV]")
nextState := ClientStateWaitCV{
AuthCertificate: state.AuthCertificate,
Params: state.Params,
cryptoParams: state.cryptoParams,
handshakeHash: state.handshakeHash,
certificates: state.certificates,
serverCertificate: cert,
serverCertificateRequest: state.serverCertificateRequest,
masterSecret: state.masterSecret,
clientHandshakeTrafficSecret: state.clientHandshakeTrafficSecret,
serverHandshakeTrafficSecret: state.serverHandshakeTrafficSecret,
}
return nextState, nil, AlertNoAlert
}
type ClientStateWaitCV struct {
AuthCertificate func(chain []CertificateEntry) error
Params ConnectionParameters
cryptoParams CipherSuiteParams
handshakeHash hash.Hash
certificates []*Certificate
serverCertificate *CertificateBody
serverCertificateRequest *CertificateRequestBody
masterSecret []byte
clientHandshakeTrafficSecret []byte
serverHandshakeTrafficSecret []byte
}
func (state ClientStateWaitCV) Next(hm *HandshakeMessage) (HandshakeState, []HandshakeAction, Alert) {
if hm == nil || hm.msgType != HandshakeTypeCertificateVerify {
logf(logTypeHandshake, "[ClientStateWaitCV] Unexpected message")
return nil, nil, AlertUnexpectedMessage
}
certVerify := CertificateVerifyBody{}
_, err := certVerify.Unmarshal(hm.body)
if err != nil {
logf(logTypeHandshake, "[ClientStateWaitCV] Error decoding message: %v", err)
return nil, nil, AlertDecodeError
}
hcv := state.handshakeHash.Sum(nil)
logf(logTypeHandshake, "Handshake Hash to be verified: [%d] %x", len(hcv), hcv)
serverPublicKey := state.serverCertificate.CertificateList[0].CertData.PublicKey
if err := certVerify.Verify(serverPublicKey, hcv); err != nil {
logf(logTypeHandshake, "[ClientStateWaitCV] Server signature failed to verify")
return nil, nil, AlertHandshakeFailure
}
if state.AuthCertificate != nil {
err := state.AuthCertificate(state.serverCertificate.CertificateList)
if err != nil {
logf(logTypeHandshake, "[ClientStateWaitCV] Application rejected server certificate")
return nil, nil, AlertBadCertificate
}
} else {
logf(logTypeHandshake, "[ClientStateWaitCV] WARNING: No verification of server certificate")
}
state.handshakeHash.Write(hm.Marshal())
logf(logTypeHandshake, "[ClientStateWaitCV] -> [ClientStateWaitFinished]")
nextState := ClientStateWaitFinished{
Params: state.Params,
cryptoParams: state.cryptoParams,
handshakeHash: state.handshakeHash,
certificates: state.certificates,
serverCertificateRequest: state.serverCertificateRequest,
masterSecret: state.masterSecret,
clientHandshakeTrafficSecret: state.clientHandshakeTrafficSecret,
serverHandshakeTrafficSecret: state.serverHandshakeTrafficSecret,
}
return nextState, nil, AlertNoAlert
}
type ClientStateWaitFinished struct {
Params ConnectionParameters
cryptoParams CipherSuiteParams
handshakeHash hash.Hash
certificates []*Certificate
serverCertificateRequest *CertificateRequestBody
masterSecret []byte
clientHandshakeTrafficSecret []byte
serverHandshakeTrafficSecret []byte
}
func (state ClientStateWaitFinished) Next(hm *HandshakeMessage) (HandshakeState, []HandshakeAction, Alert) {
if hm == nil || hm.msgType != HandshakeTypeFinished {
logf(logTypeHandshake, "[ClientStateWaitFinished] Unexpected message")
return nil, nil, AlertUnexpectedMessage
}
// Verify server's Finished
h3 := state.handshakeHash.Sum(nil)
logf(logTypeCrypto, "handshake hash 3 [%d] %x", len(h3), h3)
logf(logTypeCrypto, "handshake hash for server Finished: [%d] %x", len(h3), h3)
serverFinishedData := computeFinishedData(state.cryptoParams, state.serverHandshakeTrafficSecret, h3)
logf(logTypeCrypto, "server finished data: [%d] %x", len(serverFinishedData), serverFinishedData)
fin := &FinishedBody{VerifyDataLen: len(serverFinishedData)}
_, err := fin.Unmarshal(hm.body)
if err != nil {
logf(logTypeHandshake, "[ClientStateWaitFinished] Error decoding message: %v", err)
return nil, nil, AlertDecodeError
}
if !bytes.Equal(fin.VerifyData, serverFinishedData) {
logf(logTypeHandshake, "[ClientStateWaitFinished] Server's Finished failed to verify [%x] != [%x]",
fin.VerifyData, serverFinishedData)
return nil, nil, AlertHandshakeFailure
}
// Update the handshake hash with the Finished
state.handshakeHash.Write(hm.Marshal())
logf(logTypeCrypto, "input to handshake hash [%d]: %x", len(hm.Marshal()), hm.Marshal())
h4 := state.handshakeHash.Sum(nil)
logf(logTypeCrypto, "handshake hash 4 [%d]: %x", len(h4), h4)
// Compute traffic secrets and keys
clientTrafficSecret := deriveSecret(state.cryptoParams, state.masterSecret, labelClientApplicationTrafficSecret, h4)
serverTrafficSecret := deriveSecret(state.cryptoParams, state.masterSecret, labelServerApplicationTrafficSecret, h4)
logf(logTypeCrypto, "client traffic secret: [%d] %x", len(clientTrafficSecret), clientTrafficSecret)
logf(logTypeCrypto, "server traffic secret: [%d] %x", len(serverTrafficSecret), serverTrafficSecret)
clientTrafficKeys := makeTrafficKeys(state.cryptoParams, clientTrafficSecret)
serverTrafficKeys := makeTrafficKeys(state.cryptoParams, serverTrafficSecret)
exporterSecret := deriveSecret(state.cryptoParams, state.masterSecret, labelExporterSecret, h4)
logf(logTypeCrypto, "client exporter secret: [%d] %x", len(exporterSecret), exporterSecret)
// Assemble client's second flight
toSend := []HandshakeAction{}
if state.Params.UsingEarlyData {
// Note: We only send EOED if the server is actually going to use the early
// data. Otherwise, it will never see it, and the transcripts will
// mismatch.
// EOED marshal is infallible
eoedm, _ := HandshakeMessageFromBody(&EndOfEarlyDataBody{})
toSend = append(toSend, SendHandshakeMessage{eoedm})
state.handshakeHash.Write(eoedm.Marshal())
logf(logTypeCrypto, "input to handshake hash [%d]: %x", len(eoedm.Marshal()), eoedm.Marshal())
}
clientHandshakeKeys := makeTrafficKeys(state.cryptoParams, state.clientHandshakeTrafficSecret)
toSend = append(toSend, RekeyOut{Label: "handshake", KeySet: clientHandshakeKeys})
if state.Params.UsingClientAuth {
// Extract constraints from certicateRequest
schemes := SignatureAlgorithmsExtension{}
gotSchemes := state.serverCertificateRequest.Extensions.Find(&schemes)
if !gotSchemes {
logf(logTypeHandshake, "[ClientStateWaitFinished] WARNING no appropriate certificate found [%v]", err)
return nil, nil, AlertIllegalParameter
}
// Select a certificate
cert, certScheme, err := CertificateSelection(nil, schemes.Algorithms, state.certificates)
if err != nil {
// XXX: Signal this to the application layer?
logf(logTypeHandshake, "[ClientStateWaitFinished] WARNING no appropriate certificate found [%v]", err)
certificate := &CertificateBody{}
certm, err := HandshakeMessageFromBody(certificate)
if err != nil {
logf(logTypeHandshake, "[ClientStateWaitFinished] Error marshaling Certificate [%v]", err)
return nil, nil, AlertInternalError
}
toSend = append(toSend, SendHandshakeMessage{certm})
state.handshakeHash.Write(certm.Marshal())
} else {
// Create and send Certificate, CertificateVerify
certificate := &CertificateBody{
CertificateList: make([]CertificateEntry, len(cert.Chain)),
}
for i, entry := range cert.Chain {
certificate.CertificateList[i] = CertificateEntry{CertData: entry}
}
certm, err := HandshakeMessageFromBody(certificate)
if err != nil {
logf(logTypeHandshake, "[ClientStateWaitFinished] Error marshaling Certificate [%v]", err)
return nil, nil, AlertInternalError
}
toSend = append(toSend, SendHandshakeMessage{certm})
state.handshakeHash.Write(certm.Marshal())
hcv := state.handshakeHash.Sum(nil)
logf(logTypeHandshake, "Handshake Hash to be verified: [%d] %x", len(hcv), hcv)
certificateVerify := &CertificateVerifyBody{Algorithm: certScheme}
logf(logTypeHandshake, "Creating CertVerify: %04x %v", certScheme, state.cryptoParams.Hash)
err = certificateVerify.Sign(cert.PrivateKey, hcv)
if err != nil {
logf(logTypeHandshake, "[ClientStateWaitFinished] Error signing CertificateVerify [%v]", err)
return nil, nil, AlertInternalError
}
certvm, err := HandshakeMessageFromBody(certificateVerify)
if err != nil {
logf(logTypeHandshake, "[ClientStateWaitFinished] Error marshaling CertificateVerify [%v]", err)
return nil, nil, AlertInternalError
}
toSend = append(toSend, SendHandshakeMessage{certvm})
state.handshakeHash.Write(certvm.Marshal())
}
}
// Compute the client's Finished message
h5 := state.handshakeHash.Sum(nil)
logf(logTypeCrypto, "handshake hash for client Finished: [%d] %x", len(h5), h5)
clientFinishedData := computeFinishedData(state.cryptoParams, state.clientHandshakeTrafficSecret, h5)
logf(logTypeCrypto, "client Finished data: [%d] %x", len(clientFinishedData), clientFinishedData)
fin = &FinishedBody{
VerifyDataLen: len(clientFinishedData),
VerifyData: clientFinishedData,
}
finm, err := HandshakeMessageFromBody(fin)
if err != nil {
logf(logTypeHandshake, "[ClientStateWaitFinished] Error marshaling client Finished [%v]", err)
return nil, nil, AlertInternalError
}
// Compute the resumption secret
state.handshakeHash.Write(finm.Marshal())
h6 := state.handshakeHash.Sum(nil)
resumptionSecret := deriveSecret(state.cryptoParams, state.masterSecret, labelResumptionSecret, h6)
logf(logTypeCrypto, "resumption secret: [%d] %x", len(resumptionSecret), resumptionSecret)
toSend = append(toSend, []HandshakeAction{
SendHandshakeMessage{finm},
RekeyIn{Label: "application", KeySet: serverTrafficKeys},
RekeyOut{Label: "application", KeySet: clientTrafficKeys},
}...)
logf(logTypeHandshake, "[ClientStateWaitFinished] -> [StateConnected]")
nextState := StateConnected{
Params: state.Params,
isClient: true,
cryptoParams: state.cryptoParams,
resumptionSecret: resumptionSecret,
clientTrafficSecret: clientTrafficSecret,
serverTrafficSecret: serverTrafficSecret,
exporterSecret: exporterSecret,
}
return nextState, toSend, AlertNoAlert
}

View File

@ -1,152 +0,0 @@
package mint
import (
"fmt"
"strconv"
)
var (
supportedVersion uint16 = 0x7f15 // draft-21
// Flags for some minor compat issues
allowWrongVersionNumber = true
allowPKCS1 = true
)
// enum {...} ContentType;
type RecordType byte
const (
RecordTypeAlert RecordType = 21
RecordTypeHandshake RecordType = 22
RecordTypeApplicationData RecordType = 23
)
// enum {...} HandshakeType;
type HandshakeType byte
const (
// Omitted: *_RESERVED
HandshakeTypeClientHello HandshakeType = 1
HandshakeTypeServerHello HandshakeType = 2
HandshakeTypeNewSessionTicket HandshakeType = 4
HandshakeTypeEndOfEarlyData HandshakeType = 5
HandshakeTypeHelloRetryRequest HandshakeType = 6
HandshakeTypeEncryptedExtensions HandshakeType = 8
HandshakeTypeCertificate HandshakeType = 11
HandshakeTypeCertificateRequest HandshakeType = 13
HandshakeTypeCertificateVerify HandshakeType = 15
HandshakeTypeServerConfiguration HandshakeType = 17
HandshakeTypeFinished HandshakeType = 20
HandshakeTypeKeyUpdate HandshakeType = 24
HandshakeTypeMessageHash HandshakeType = 254
)
// uint8 CipherSuite[2];
type CipherSuite uint16
const (
// XXX: Actually TLS_NULL_WITH_NULL_NULL, but we need a way to label the zero
// value for this type so that we can detect when a field is set.
CIPHER_SUITE_UNKNOWN CipherSuite = 0x0000
TLS_AES_128_GCM_SHA256 CipherSuite = 0x1301
TLS_AES_256_GCM_SHA384 CipherSuite = 0x1302
TLS_CHACHA20_POLY1305_SHA256 CipherSuite = 0x1303
TLS_AES_128_CCM_SHA256 CipherSuite = 0x1304
TLS_AES_256_CCM_8_SHA256 CipherSuite = 0x1305
)
func (c CipherSuite) String() string {
switch c {
case CIPHER_SUITE_UNKNOWN:
return "unknown"
case TLS_AES_128_GCM_SHA256:
return "TLS_AES_128_GCM_SHA256"
case TLS_AES_256_GCM_SHA384:
return "TLS_AES_256_GCM_SHA384"
case TLS_CHACHA20_POLY1305_SHA256:
return "TLS_CHACHA20_POLY1305_SHA256"
case TLS_AES_128_CCM_SHA256:
return "TLS_AES_128_CCM_SHA256"
case TLS_AES_256_CCM_8_SHA256:
return "TLS_AES_256_CCM_8_SHA256"
}
// cannot use %x here, since it calls String(), leading to infinite recursion
return fmt.Sprintf("invalid CipherSuite value: 0x%s", strconv.FormatUint(uint64(c), 16))
}
// enum {...} SignatureScheme
type SignatureScheme uint16
const (
// RSASSA-PKCS1-v1_5 algorithms
RSA_PKCS1_SHA1 SignatureScheme = 0x0201
RSA_PKCS1_SHA256 SignatureScheme = 0x0401
RSA_PKCS1_SHA384 SignatureScheme = 0x0501
RSA_PKCS1_SHA512 SignatureScheme = 0x0601
// ECDSA algorithms
ECDSA_P256_SHA256 SignatureScheme = 0x0403
ECDSA_P384_SHA384 SignatureScheme = 0x0503
ECDSA_P521_SHA512 SignatureScheme = 0x0603
// RSASSA-PSS algorithms
RSA_PSS_SHA256 SignatureScheme = 0x0804
RSA_PSS_SHA384 SignatureScheme = 0x0805
RSA_PSS_SHA512 SignatureScheme = 0x0806
// EdDSA algorithms
Ed25519 SignatureScheme = 0x0807
Ed448 SignatureScheme = 0x0808
)
// enum {...} ExtensionType
type ExtensionType uint16
const (
ExtensionTypeServerName ExtensionType = 0
ExtensionTypeSupportedGroups ExtensionType = 10
ExtensionTypeSignatureAlgorithms ExtensionType = 13
ExtensionTypeALPN ExtensionType = 16
ExtensionTypeKeyShare ExtensionType = 40
ExtensionTypePreSharedKey ExtensionType = 41
ExtensionTypeEarlyData ExtensionType = 42
ExtensionTypeSupportedVersions ExtensionType = 43
ExtensionTypeCookie ExtensionType = 44
ExtensionTypePSKKeyExchangeModes ExtensionType = 45
ExtensionTypeTicketEarlyDataInfo ExtensionType = 46
)
// enum {...} NamedGroup
type NamedGroup uint16
const (
// Elliptic Curve Groups.
P256 NamedGroup = 23
P384 NamedGroup = 24
P521 NamedGroup = 25
// ECDH functions.
X25519 NamedGroup = 29
X448 NamedGroup = 30
// Finite field groups.
FFDHE2048 NamedGroup = 256
FFDHE3072 NamedGroup = 257
FFDHE4096 NamedGroup = 258
FFDHE6144 NamedGroup = 259
FFDHE8192 NamedGroup = 260
)
// enum {...} PskKeyExchangeMode;
type PSKKeyExchangeMode uint8
const (
PSKModeKE PSKKeyExchangeMode = 0
PSKModeDHEKE PSKKeyExchangeMode = 1
)
// enum {
// update_not_requested(0), update_requested(1), (255)
// } KeyUpdateRequest;
type KeyUpdateRequest uint8
const (
KeyUpdateNotRequested KeyUpdateRequest = 0
KeyUpdateRequested KeyUpdateRequest = 1
)

View File

@ -1,819 +0,0 @@
package mint
import (
"crypto"
"crypto/x509"
"encoding/hex"
"fmt"
"io"
"net"
"reflect"
"sync"
"time"
)
var WouldBlock = fmt.Errorf("Would have blocked")
type Certificate struct {
Chain []*x509.Certificate
PrivateKey crypto.Signer
}
type PreSharedKey struct {
CipherSuite CipherSuite
IsResumption bool
Identity []byte
Key []byte
NextProto string
ReceivedAt time.Time
ExpiresAt time.Time
TicketAgeAdd uint32
}
type PreSharedKeyCache interface {
Get(string) (PreSharedKey, bool)
Put(string, PreSharedKey)
Size() int
}
type PSKMapCache map[string]PreSharedKey
// A CookieHandler does two things:
// - generates a byte string that is sent as a part of a cookie to the client in the HelloRetryRequest
// - validates this byte string echoed by the client in the ClientHello
type CookieHandler interface {
Generate(*Conn) ([]byte, error)
Validate(*Conn, []byte) bool
}
func (cache PSKMapCache) Get(key string) (psk PreSharedKey, ok bool) {
psk, ok = cache[key]
return
}
func (cache *PSKMapCache) Put(key string, psk PreSharedKey) {
(*cache)[key] = psk
}
func (cache PSKMapCache) Size() int {
return len(cache)
}
// Config is the struct used to pass configuration settings to a TLS client or
// server instance. The settings for client and server are pretty different,
// but we just throw them all in here.
type Config struct {
// Client fields
ServerName string
// Server fields
SendSessionTickets bool
TicketLifetime uint32
TicketLen int
EarlyDataLifetime uint32
AllowEarlyData bool
// Require the client to echo a cookie.
RequireCookie bool
// If cookies are required and no CookieHandler is set, a default cookie handler is used.
// The default cookie handler uses 32 random bytes as a cookie.
CookieHandler CookieHandler
RequireClientAuth bool
// Shared fields
Certificates []*Certificate
AuthCertificate func(chain []CertificateEntry) error
CipherSuites []CipherSuite
Groups []NamedGroup
SignatureSchemes []SignatureScheme
NextProtos []string
PSKs PreSharedKeyCache
PSKModes []PSKKeyExchangeMode
NonBlocking bool
// The same config object can be shared among different connections, so it
// needs its own mutex
mutex sync.RWMutex
}
// Clone returns a shallow clone of c. It is safe to clone a Config that is
// being used concurrently by a TLS client or server.
func (c *Config) Clone() *Config {
c.mutex.Lock()
defer c.mutex.Unlock()
return &Config{
ServerName: c.ServerName,
SendSessionTickets: c.SendSessionTickets,
TicketLifetime: c.TicketLifetime,
TicketLen: c.TicketLen,
EarlyDataLifetime: c.EarlyDataLifetime,
AllowEarlyData: c.AllowEarlyData,
RequireCookie: c.RequireCookie,
RequireClientAuth: c.RequireClientAuth,
Certificates: c.Certificates,
AuthCertificate: c.AuthCertificate,
CipherSuites: c.CipherSuites,
Groups: c.Groups,
SignatureSchemes: c.SignatureSchemes,
NextProtos: c.NextProtos,
PSKs: c.PSKs,
PSKModes: c.PSKModes,
NonBlocking: c.NonBlocking,
}
}
func (c *Config) Init(isClient bool) error {
c.mutex.Lock()
defer c.mutex.Unlock()
// Set defaults
if len(c.CipherSuites) == 0 {
c.CipherSuites = defaultSupportedCipherSuites
}
if len(c.Groups) == 0 {
c.Groups = defaultSupportedGroups
}
if len(c.SignatureSchemes) == 0 {
c.SignatureSchemes = defaultSignatureSchemes
}
if c.TicketLen == 0 {
c.TicketLen = defaultTicketLen
}
if !reflect.ValueOf(c.PSKs).IsValid() {
c.PSKs = &PSKMapCache{}
}
if len(c.PSKModes) == 0 {
c.PSKModes = defaultPSKModes
}
// If there is no certificate, generate one
if !isClient && len(c.Certificates) == 0 {
logf(logTypeHandshake, "Generating key name=%v", c.ServerName)
priv, err := newSigningKey(RSA_PSS_SHA256)
if err != nil {
return err
}
cert, err := newSelfSigned(c.ServerName, RSA_PKCS1_SHA256, priv)
if err != nil {
return err
}
c.Certificates = []*Certificate{
{
Chain: []*x509.Certificate{cert},
PrivateKey: priv,
},
}
}
return nil
}
func (c *Config) ValidForServer() bool {
return (reflect.ValueOf(c.PSKs).IsValid() && c.PSKs.Size() > 0) ||
(len(c.Certificates) > 0 &&
len(c.Certificates[0].Chain) > 0 &&
c.Certificates[0].PrivateKey != nil)
}
func (c *Config) ValidForClient() bool {
return len(c.ServerName) > 0
}
var (
defaultSupportedCipherSuites = []CipherSuite{
TLS_AES_128_GCM_SHA256,
TLS_AES_256_GCM_SHA384,
}
defaultSupportedGroups = []NamedGroup{
P256,
P384,
FFDHE2048,
X25519,
}
defaultSignatureSchemes = []SignatureScheme{
RSA_PSS_SHA256,
RSA_PSS_SHA384,
RSA_PSS_SHA512,
ECDSA_P256_SHA256,
ECDSA_P384_SHA384,
ECDSA_P521_SHA512,
}
defaultTicketLen = 16
defaultPSKModes = []PSKKeyExchangeMode{
PSKModeKE,
PSKModeDHEKE,
}
)
type ConnectionState struct {
HandshakeState string // string representation of the handshake state.
CipherSuite CipherSuiteParams // cipher suite in use (TLS_RSA_WITH_RC4_128_SHA, ...)
PeerCertificates []*x509.Certificate // certificate chain presented by remote peer TODO(ekr@rtfm.com): implement
NextProto string // Selected ALPN proto
}
// Conn implements the net.Conn interface, as with "crypto/tls"
// * Read, Write, and Close are provided locally
// * LocalAddr, RemoteAddr, and Set*Deadline are forwarded to the inner Conn
type Conn struct {
config *Config
conn net.Conn
isClient bool
EarlyData []byte
state StateConnected
hState HandshakeState
handshakeMutex sync.Mutex
handshakeAlert Alert
handshakeComplete bool
readBuffer []byte
in, out *RecordLayer
hIn, hOut *HandshakeLayer
extHandler AppExtensionHandler
}
func NewConn(conn net.Conn, config *Config, isClient bool) *Conn {
c := &Conn{conn: conn, config: config, isClient: isClient}
c.in = NewRecordLayer(c.conn)
c.out = NewRecordLayer(c.conn)
c.hIn = NewHandshakeLayer(c.in)
c.hIn.nonblocking = c.config.NonBlocking
c.hOut = NewHandshakeLayer(c.out)
return c
}
// Read up
func (c *Conn) consumeRecord() error {
pt, err := c.in.ReadRecord()
if pt == nil {
logf(logTypeIO, "extendBuffer returns error %v", err)
return err
}
switch pt.contentType {
case RecordTypeHandshake:
logf(logTypeHandshake, "Received post-handshake message")
// We do not support fragmentation of post-handshake handshake messages.
// TODO: Factor this more elegantly; coalesce with handshakeLayer.ReadMessage()
start := 0
for start < len(pt.fragment) {
if len(pt.fragment[start:]) < handshakeHeaderLen {
return fmt.Errorf("Post-handshake handshake message too short for header")
}
hm := &HandshakeMessage{}
hm.msgType = HandshakeType(pt.fragment[start])
hmLen := (int(pt.fragment[start+1]) << 16) + (int(pt.fragment[start+2]) << 8) + int(pt.fragment[start+3])
if len(pt.fragment[start+handshakeHeaderLen:]) < hmLen {
return fmt.Errorf("Post-handshake handshake message too short for body")
}
hm.body = pt.fragment[start+handshakeHeaderLen : start+handshakeHeaderLen+hmLen]
// Advance state machine
state, actions, alert := c.state.Next(hm)
if alert != AlertNoAlert {
logf(logTypeHandshake, "Error in state transition: %v", alert)
c.sendAlert(alert)
return io.EOF
}
for _, action := range actions {
alert = c.takeAction(action)
if alert != AlertNoAlert {
logf(logTypeHandshake, "Error during handshake actions: %v", alert)
c.sendAlert(alert)
return io.EOF
}
}
// XXX: If we want to support more advanced cases, e.g., post-handshake
// authentication, we'll need to allow transitions other than
// Connected -> Connected
var connected bool
c.state, connected = state.(StateConnected)
if !connected {
logf(logTypeHandshake, "Disconnected after state transition: %v", alert)
c.sendAlert(alert)
return io.EOF
}
start += handshakeHeaderLen + hmLen
}
case RecordTypeAlert:
logf(logTypeIO, "extended buffer (for alert): [%d] %x", len(c.readBuffer), c.readBuffer)
if len(pt.fragment) != 2 {
c.sendAlert(AlertUnexpectedMessage)
return io.EOF
}
if Alert(pt.fragment[1]) == AlertCloseNotify {
return io.EOF
}
switch pt.fragment[0] {
case AlertLevelWarning:
// drop on the floor
case AlertLevelError:
return Alert(pt.fragment[1])
default:
c.sendAlert(AlertUnexpectedMessage)
return io.EOF
}
case RecordTypeApplicationData:
c.readBuffer = append(c.readBuffer, pt.fragment...)
logf(logTypeIO, "extended buffer: [%d] %x", len(c.readBuffer), c.readBuffer)
}
return err
}
// Read application data up to the size of buffer. Handshake and alert records
// are consumed by the Conn object directly.
func (c *Conn) Read(buffer []byte) (int, error) {
logf(logTypeHandshake, "conn.Read with buffer = %d", len(buffer))
if alert := c.Handshake(); alert != AlertNoAlert {
return 0, alert
}
if len(buffer) == 0 {
return 0, nil
}
// Lock the input channel
c.in.Lock()
defer c.in.Unlock()
for len(c.readBuffer) == 0 {
err := c.consumeRecord()
// err can be nil if consumeRecord processed a non app-data
// record.
if err != nil {
if c.config.NonBlocking || err != WouldBlock {
logf(logTypeIO, "conn.Read returns err=%v", err)
return 0, err
}
}
}
var read int
n := len(buffer)
logf(logTypeIO, "conn.Read input buffer now has len %d", len(c.readBuffer))
if len(c.readBuffer) <= n {
buffer = buffer[:len(c.readBuffer)]
copy(buffer, c.readBuffer)
read = len(c.readBuffer)
c.readBuffer = c.readBuffer[:0]
} else {
logf(logTypeIO, "read buffer larger than input buffer (%d > %d)", len(c.readBuffer), n)
copy(buffer[:n], c.readBuffer[:n])
c.readBuffer = c.readBuffer[n:]
read = n
}
logf(logTypeVerbose, "Returning %v", string(buffer))
return read, nil
}
// Write application data
func (c *Conn) Write(buffer []byte) (int, error) {
// Lock the output channel
c.out.Lock()
defer c.out.Unlock()
// Send full-size fragments
var start int
sent := 0
for start = 0; len(buffer)-start >= maxFragmentLen; start += maxFragmentLen {
err := c.out.WriteRecord(&TLSPlaintext{
contentType: RecordTypeApplicationData,
fragment: buffer[start : start+maxFragmentLen],
})
if err != nil {
return sent, err
}
sent += maxFragmentLen
}
// Send a final partial fragment if necessary
if start < len(buffer) {
err := c.out.WriteRecord(&TLSPlaintext{
contentType: RecordTypeApplicationData,
fragment: buffer[start:],
})
if err != nil {
return sent, err
}
sent += len(buffer[start:])
}
return sent, nil
}
// sendAlert sends a TLS alert message.
// c.out.Mutex <= L.
func (c *Conn) sendAlert(err Alert) error {
c.handshakeMutex.Lock()
defer c.handshakeMutex.Unlock()
var level int
switch err {
case AlertNoRenegotiation, AlertCloseNotify:
level = AlertLevelWarning
default:
level = AlertLevelError
}
buf := []byte{byte(err), byte(level)}
c.out.WriteRecord(&TLSPlaintext{
contentType: RecordTypeAlert,
fragment: buf,
})
// close_notify and end_of_early_data are not actually errors
if level == AlertLevelWarning {
return &net.OpError{Op: "local error", Err: err}
}
return c.Close()
}
// Close closes the connection.
func (c *Conn) Close() error {
// XXX crypto/tls has an interlock with Write here. Do we need that?
return c.conn.Close()
}
// LocalAddr returns the local network address.
func (c *Conn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
// RemoteAddr returns the remote network address.
func (c *Conn) RemoteAddr() net.Addr {
return c.conn.RemoteAddr()
}
// SetDeadline sets the read and write deadlines associated with the connection.
// A zero value for t means Read and Write will not time out.
// After a Write has timed out, the TLS state is corrupt and all future writes will return the same error.
func (c *Conn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
// SetReadDeadline sets the read deadline on the underlying connection.
// A zero value for t means Read will not time out.
func (c *Conn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
// SetWriteDeadline sets the write deadline on the underlying connection.
// A zero value for t means Write will not time out.
// After a Write has timed out, the TLS state is corrupt and all future writes will return the same error.
func (c *Conn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
}
func (c *Conn) takeAction(actionGeneric HandshakeAction) Alert {
label := "[server]"
if c.isClient {
label = "[client]"
}
switch action := actionGeneric.(type) {
case SendHandshakeMessage:
err := c.hOut.WriteMessage(action.Message)
if err != nil {
logf(logTypeHandshake, "%s Error writing handshake message: %v", label, err)
return AlertInternalError
}
case RekeyIn:
logf(logTypeHandshake, "%s Rekeying in to %s: %+v", label, action.Label, action.KeySet)
err := c.in.Rekey(action.KeySet.cipher, action.KeySet.key, action.KeySet.iv)
if err != nil {
logf(logTypeHandshake, "%s Unable to rekey inbound: %v", label, err)
return AlertInternalError
}
case RekeyOut:
logf(logTypeHandshake, "%s Rekeying out to %s: %+v", label, action.Label, action.KeySet)
err := c.out.Rekey(action.KeySet.cipher, action.KeySet.key, action.KeySet.iv)
if err != nil {
logf(logTypeHandshake, "%s Unable to rekey outbound: %v", label, err)
return AlertInternalError
}
case SendEarlyData:
logf(logTypeHandshake, "%s Sending early data...", label)
_, err := c.Write(c.EarlyData)
if err != nil {
logf(logTypeHandshake, "%s Error writing early data: %v", label, err)
return AlertInternalError
}
case ReadPastEarlyData:
logf(logTypeHandshake, "%s Reading past early data...", label)
// Scan past all records that fail to decrypt
_, err := c.in.PeekRecordType(!c.config.NonBlocking)
if err == nil {
break
}
_, ok := err.(DecryptError)
for ok {
_, err = c.in.PeekRecordType(!c.config.NonBlocking)
if err == nil {
break
}
_, ok = err.(DecryptError)
}
case ReadEarlyData:
logf(logTypeHandshake, "%s Reading early data...", label)
t, err := c.in.PeekRecordType(!c.config.NonBlocking)
if err != nil {
logf(logTypeHandshake, "%s Error reading record type (1): %v", label, err)
return AlertInternalError
}
logf(logTypeHandshake, "%s Got record type(1): %v", label, t)
for t == RecordTypeApplicationData {
// Read a record into the buffer. Note that this is safe
// in blocking mode because we read the record in in
// PeekRecordType.
pt, err := c.in.ReadRecord()
if err != nil {
logf(logTypeHandshake, "%s Error reading early data record: %v", label, err)
return AlertInternalError
}
logf(logTypeHandshake, "%s Read early data: %x", label, pt.fragment)
c.EarlyData = append(c.EarlyData, pt.fragment...)
t, err = c.in.PeekRecordType(!c.config.NonBlocking)
if err != nil {
logf(logTypeHandshake, "%s Error reading record type (2): %v", label, err)
return AlertInternalError
}
logf(logTypeHandshake, "%s Got record type (2): %v", label, t)
}
logf(logTypeHandshake, "%s Done reading early data", label)
case StorePSK:
logf(logTypeHandshake, "%s Storing new session ticket with identity [%x]", label, action.PSK.Identity)
if c.isClient {
// Clients look up PSKs based on server name
c.config.PSKs.Put(c.config.ServerName, action.PSK)
} else {
// Servers look them up based on the identity in the extension
c.config.PSKs.Put(hex.EncodeToString(action.PSK.Identity), action.PSK)
}
default:
logf(logTypeHandshake, "%s Unknown actionuction type", label)
return AlertInternalError
}
return AlertNoAlert
}
func (c *Conn) HandshakeSetup() Alert {
var state HandshakeState
var actions []HandshakeAction
var alert Alert
if err := c.config.Init(c.isClient); err != nil {
logf(logTypeHandshake, "Error initializing config: %v", err)
return AlertInternalError
}
// Set things up
caps := Capabilities{
CipherSuites: c.config.CipherSuites,
Groups: c.config.Groups,
SignatureSchemes: c.config.SignatureSchemes,
PSKs: c.config.PSKs,
PSKModes: c.config.PSKModes,
AllowEarlyData: c.config.AllowEarlyData,
RequireCookie: c.config.RequireCookie,
CookieHandler: c.config.CookieHandler,
RequireClientAuth: c.config.RequireClientAuth,
NextProtos: c.config.NextProtos,
Certificates: c.config.Certificates,
ExtensionHandler: c.extHandler,
}
opts := ConnectionOptions{
ServerName: c.config.ServerName,
NextProtos: c.config.NextProtos,
EarlyData: c.EarlyData,
}
if caps.RequireCookie && caps.CookieHandler == nil {
caps.CookieHandler = &defaultCookieHandler{}
}
if c.isClient {
state, actions, alert = ClientStateStart{Caps: caps, Opts: opts}.Next(nil)
if alert != AlertNoAlert {
logf(logTypeHandshake, "Error initializing client state: %v", alert)
return alert
}
for _, action := range actions {
alert = c.takeAction(action)
if alert != AlertNoAlert {
logf(logTypeHandshake, "Error during handshake actions: %v", alert)
return alert
}
}
} else {
state = ServerStateStart{Caps: caps, conn: c}
}
c.hState = state
return AlertNoAlert
}
// Handshake causes a TLS handshake on the connection. The `isClient` member
// determines whether a client or server handshake is performed. If a
// handshake has already been performed, then its result will be returned.
func (c *Conn) Handshake() Alert {
label := "[server]"
if c.isClient {
label = "[client]"
}
// TODO Lock handshakeMutex
// TODO Remove CloseNotify hack
if c.handshakeAlert != AlertNoAlert && c.handshakeAlert != AlertCloseNotify {
logf(logTypeHandshake, "Pre-existing handshake error: %v", c.handshakeAlert)
return c.handshakeAlert
}
if c.handshakeComplete {
return AlertNoAlert
}
var alert Alert
if c.hState == nil {
logf(logTypeHandshake, "%s First time through handshake, setting up", label)
alert = c.HandshakeSetup()
if alert != AlertNoAlert {
return alert
}
} else {
logf(logTypeHandshake, "Re-entering handshake, state=%v", c.hState)
}
state := c.hState
_, connected := state.(StateConnected)
var actions []HandshakeAction
for !connected {
// Read a handshake message
hm, err := c.hIn.ReadMessage()
if err == WouldBlock {
logf(logTypeHandshake, "%s Would block reading message: %v", label, err)
return AlertWouldBlock
}
if err != nil {
logf(logTypeHandshake, "%s Error reading message: %v", label, err)
c.sendAlert(AlertCloseNotify)
return AlertCloseNotify
}
logf(logTypeHandshake, "Read message with type: %v", hm.msgType)
// Advance the state machine
state, actions, alert = state.Next(hm)
if alert != AlertNoAlert {
logf(logTypeHandshake, "Error in state transition: %v", alert)
return alert
}
for index, action := range actions {
logf(logTypeHandshake, "%s taking next action (%d)", label, index)
alert = c.takeAction(action)
if alert != AlertNoAlert {
logf(logTypeHandshake, "Error during handshake actions: %v", alert)
c.sendAlert(alert)
return alert
}
}
c.hState = state
logf(logTypeHandshake, "state is now %s", c.GetHsState())
_, connected = state.(StateConnected)
}
c.state = state.(StateConnected)
// Send NewSessionTicket if acting as server
if !c.isClient && c.config.SendSessionTickets {
actions, alert := c.state.NewSessionTicket(
c.config.TicketLen,
c.config.TicketLifetime,
c.config.EarlyDataLifetime)
for _, action := range actions {
alert = c.takeAction(action)
if alert != AlertNoAlert {
logf(logTypeHandshake, "Error during handshake actions: %v", alert)
c.sendAlert(alert)
return alert
}
}
}
c.handshakeComplete = true
return AlertNoAlert
}
func (c *Conn) SendKeyUpdate(requestUpdate bool) error {
if !c.handshakeComplete {
return fmt.Errorf("Cannot update keys until after handshake")
}
request := KeyUpdateNotRequested
if requestUpdate {
request = KeyUpdateRequested
}
// Create the key update and update state
actions, alert := c.state.KeyUpdate(request)
if alert != AlertNoAlert {
c.sendAlert(alert)
return fmt.Errorf("Alert while generating key update: %v", alert)
}
// Take actions (send key update and rekey)
for _, action := range actions {
alert = c.takeAction(action)
if alert != AlertNoAlert {
c.sendAlert(alert)
return fmt.Errorf("Alert during key update actions: %v", alert)
}
}
return nil
}
func (c *Conn) GetHsState() string {
return reflect.TypeOf(c.hState).Name()
}
func (c *Conn) ComputeExporter(label string, context []byte, keyLength int) ([]byte, error) {
_, connected := c.hState.(StateConnected)
if !connected {
return nil, fmt.Errorf("Cannot compute exporter when state is not connected")
}
if c.state.exporterSecret == nil {
return nil, fmt.Errorf("Internal error: no exporter secret")
}
h0 := c.state.cryptoParams.Hash.New().Sum(nil)
tmpSecret := deriveSecret(c.state.cryptoParams, c.state.exporterSecret, label, h0)
hc := c.state.cryptoParams.Hash.New().Sum(context)
return HkdfExpandLabel(c.state.cryptoParams.Hash, tmpSecret, "exporter", hc, keyLength), nil
}
func (c *Conn) State() ConnectionState {
state := ConnectionState{
HandshakeState: c.GetHsState(),
}
if c.handshakeComplete {
state.CipherSuite = cipherSuiteMap[c.state.Params.CipherSuite]
state.NextProto = c.state.Params.NextProto
}
return state
}
func (c *Conn) SetExtensionHandler(h AppExtensionHandler) error {
if c.hState != nil {
return fmt.Errorf("Can't set extension handler after setup")
}
c.extHandler = h
return nil
}

View File

@ -1,654 +0,0 @@
package mint
import (
"bytes"
"crypto"
"crypto/aes"
"crypto/cipher"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/hmac"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"fmt"
"math/big"
"time"
"golang.org/x/crypto/curve25519"
// Blank includes to ensure hash support
_ "crypto/sha1"
_ "crypto/sha256"
_ "crypto/sha512"
)
var prng = rand.Reader
type aeadFactory func(key []byte) (cipher.AEAD, error)
type CipherSuiteParams struct {
Suite CipherSuite
Cipher aeadFactory // Cipher factory
Hash crypto.Hash // Hash function
KeyLen int // Key length in octets
IvLen int // IV length in octets
}
type signatureAlgorithm uint8
const (
signatureAlgorithmUnknown = iota
signatureAlgorithmRSA_PKCS1
signatureAlgorithmRSA_PSS
signatureAlgorithmECDSA
)
var (
hashMap = map[SignatureScheme]crypto.Hash{
RSA_PKCS1_SHA1: crypto.SHA1,
RSA_PKCS1_SHA256: crypto.SHA256,
RSA_PKCS1_SHA384: crypto.SHA384,
RSA_PKCS1_SHA512: crypto.SHA512,
ECDSA_P256_SHA256: crypto.SHA256,
ECDSA_P384_SHA384: crypto.SHA384,
ECDSA_P521_SHA512: crypto.SHA512,
RSA_PSS_SHA256: crypto.SHA256,
RSA_PSS_SHA384: crypto.SHA384,
RSA_PSS_SHA512: crypto.SHA512,
}
sigMap = map[SignatureScheme]signatureAlgorithm{
RSA_PKCS1_SHA1: signatureAlgorithmRSA_PKCS1,
RSA_PKCS1_SHA256: signatureAlgorithmRSA_PKCS1,
RSA_PKCS1_SHA384: signatureAlgorithmRSA_PKCS1,
RSA_PKCS1_SHA512: signatureAlgorithmRSA_PKCS1,
ECDSA_P256_SHA256: signatureAlgorithmECDSA,
ECDSA_P384_SHA384: signatureAlgorithmECDSA,
ECDSA_P521_SHA512: signatureAlgorithmECDSA,
RSA_PSS_SHA256: signatureAlgorithmRSA_PSS,
RSA_PSS_SHA384: signatureAlgorithmRSA_PSS,
RSA_PSS_SHA512: signatureAlgorithmRSA_PSS,
}
curveMap = map[SignatureScheme]NamedGroup{
ECDSA_P256_SHA256: P256,
ECDSA_P384_SHA384: P384,
ECDSA_P521_SHA512: P521,
}
newAESGCM = func(key []byte) (cipher.AEAD, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
// TLS always uses 12-byte nonces
return cipher.NewGCMWithNonceSize(block, 12)
}
cipherSuiteMap = map[CipherSuite]CipherSuiteParams{
TLS_AES_128_GCM_SHA256: {
Suite: TLS_AES_128_GCM_SHA256,
Cipher: newAESGCM,
Hash: crypto.SHA256,
KeyLen: 16,
IvLen: 12,
},
TLS_AES_256_GCM_SHA384: {
Suite: TLS_AES_256_GCM_SHA384,
Cipher: newAESGCM,
Hash: crypto.SHA384,
KeyLen: 32,
IvLen: 12,
},
}
x509AlgMap = map[SignatureScheme]x509.SignatureAlgorithm{
RSA_PKCS1_SHA1: x509.SHA1WithRSA,
RSA_PKCS1_SHA256: x509.SHA256WithRSA,
RSA_PKCS1_SHA384: x509.SHA384WithRSA,
RSA_PKCS1_SHA512: x509.SHA512WithRSA,
ECDSA_P256_SHA256: x509.ECDSAWithSHA256,
ECDSA_P384_SHA384: x509.ECDSAWithSHA384,
ECDSA_P521_SHA512: x509.ECDSAWithSHA512,
}
defaultRSAKeySize = 2048
)
func curveFromNamedGroup(group NamedGroup) (crv elliptic.Curve) {
switch group {
case P256:
crv = elliptic.P256()
case P384:
crv = elliptic.P384()
case P521:
crv = elliptic.P521()
}
return
}
func namedGroupFromECDSAKey(key *ecdsa.PublicKey) (g NamedGroup) {
switch key.Curve.Params().Name {
case elliptic.P256().Params().Name:
g = P256
case elliptic.P384().Params().Name:
g = P384
case elliptic.P521().Params().Name:
g = P521
}
return
}
func keyExchangeSizeFromNamedGroup(group NamedGroup) (size int) {
size = 0
switch group {
case X25519:
size = 32
case P256:
size = 65
case P384:
size = 97
case P521:
size = 133
case FFDHE2048:
size = 256
case FFDHE3072:
size = 384
case FFDHE4096:
size = 512
case FFDHE6144:
size = 768
case FFDHE8192:
size = 1024
}
return
}
func primeFromNamedGroup(group NamedGroup) (p *big.Int) {
switch group {
case FFDHE2048:
p = finiteFieldPrime2048
case FFDHE3072:
p = finiteFieldPrime3072
case FFDHE4096:
p = finiteFieldPrime4096
case FFDHE6144:
p = finiteFieldPrime6144
case FFDHE8192:
p = finiteFieldPrime8192
}
return
}
func schemeValidForKey(alg SignatureScheme, key crypto.Signer) bool {
sigType := sigMap[alg]
switch key.(type) {
case *rsa.PrivateKey:
return sigType == signatureAlgorithmRSA_PKCS1 || sigType == signatureAlgorithmRSA_PSS
case *ecdsa.PrivateKey:
return sigType == signatureAlgorithmECDSA
default:
return false
}
}
func ffdheKeyShareFromPrime(p *big.Int) (priv, pub *big.Int, err error) {
primeLen := len(p.Bytes())
for {
// g = 2 for all ffdhe groups
priv, err = rand.Int(prng, p)
if err != nil {
return
}
pub = big.NewInt(0)
pub.Exp(big.NewInt(2), priv, p)
if len(pub.Bytes()) == primeLen {
return
}
}
}
func newKeyShare(group NamedGroup) (pub []byte, priv []byte, err error) {
switch group {
case P256, P384, P521:
var x, y *big.Int
crv := curveFromNamedGroup(group)
priv, x, y, err = elliptic.GenerateKey(crv, prng)
if err != nil {
return
}
pub = elliptic.Marshal(crv, x, y)
return
case FFDHE2048, FFDHE3072, FFDHE4096, FFDHE6144, FFDHE8192:
p := primeFromNamedGroup(group)
x, X, err2 := ffdheKeyShareFromPrime(p)
if err2 != nil {
err = err2
return
}
priv = x.Bytes()
pubBytes := X.Bytes()
numBytes := keyExchangeSizeFromNamedGroup(group)
pub = make([]byte, numBytes)
copy(pub[numBytes-len(pubBytes):], pubBytes)
return
case X25519:
var private, public [32]byte
_, err = prng.Read(private[:])
if err != nil {
return
}
curve25519.ScalarBaseMult(&public, &private)
priv = private[:]
pub = public[:]
return
default:
return nil, nil, fmt.Errorf("tls.newkeyshare: Unsupported group %v", group)
}
}
func keyAgreement(group NamedGroup, pub []byte, priv []byte) ([]byte, error) {
switch group {
case P256, P384, P521:
if len(pub) != keyExchangeSizeFromNamedGroup(group) {
return nil, fmt.Errorf("tls.keyagreement: Wrong public key size")
}
crv := curveFromNamedGroup(group)
pubX, pubY := elliptic.Unmarshal(crv, pub)
x, _ := crv.Params().ScalarMult(pubX, pubY, priv)
xBytes := x.Bytes()
numBytes := len(crv.Params().P.Bytes())
ret := make([]byte, numBytes)
copy(ret[numBytes-len(xBytes):], xBytes)
return ret, nil
case FFDHE2048, FFDHE3072, FFDHE4096, FFDHE6144, FFDHE8192:
numBytes := keyExchangeSizeFromNamedGroup(group)
if len(pub) != numBytes {
return nil, fmt.Errorf("tls.keyagreement: Wrong public key size")
}
p := primeFromNamedGroup(group)
x := big.NewInt(0).SetBytes(priv)
Y := big.NewInt(0).SetBytes(pub)
ZBytes := big.NewInt(0).Exp(Y, x, p).Bytes()
ret := make([]byte, numBytes)
copy(ret[numBytes-len(ZBytes):], ZBytes)
return ret, nil
case X25519:
if len(pub) != keyExchangeSizeFromNamedGroup(group) {
return nil, fmt.Errorf("tls.keyagreement: Wrong public key size")
}
var private, public, ret [32]byte
copy(private[:], priv)
copy(public[:], pub)
curve25519.ScalarMult(&ret, &private, &public)
return ret[:], nil
default:
return nil, fmt.Errorf("tls.keyagreement: Unsupported group %v", group)
}
}
func newSigningKey(sig SignatureScheme) (crypto.Signer, error) {
switch sig {
case RSA_PKCS1_SHA1, RSA_PKCS1_SHA256,
RSA_PKCS1_SHA384, RSA_PKCS1_SHA512,
RSA_PSS_SHA256, RSA_PSS_SHA384,
RSA_PSS_SHA512:
return rsa.GenerateKey(prng, defaultRSAKeySize)
case ECDSA_P256_SHA256:
return ecdsa.GenerateKey(elliptic.P256(), prng)
case ECDSA_P384_SHA384:
return ecdsa.GenerateKey(elliptic.P384(), prng)
case ECDSA_P521_SHA512:
return ecdsa.GenerateKey(elliptic.P521(), prng)
default:
return nil, fmt.Errorf("tls.newsigningkey: Unsupported signature algorithm [%04x]", sig)
}
}
func newSelfSigned(name string, alg SignatureScheme, priv crypto.Signer) (*x509.Certificate, error) {
sigAlg, ok := x509AlgMap[alg]
if !ok {
return nil, fmt.Errorf("tls.selfsigned: Unknown signature algorithm [%04x]", alg)
}
if len(name) == 0 {
return nil, fmt.Errorf("tls.selfsigned: No name provided")
}
serial, err := rand.Int(rand.Reader, big.NewInt(0xA0A0A0A0))
if err != nil {
return nil, err
}
template := &x509.Certificate{
SerialNumber: serial,
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(0, 0, 1),
SignatureAlgorithm: sigAlg,
Subject: pkix.Name{CommonName: name},
DNSNames: []string{name},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyAgreement | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
}
der, err := x509.CreateCertificate(prng, template, template, priv.Public(), priv)
if err != nil {
return nil, err
}
// It is safe to ignore the error here because we're parsing known-good data
cert, _ := x509.ParseCertificate(der)
return cert, nil
}
// XXX(rlb): Copied from crypto/x509
type ecdsaSignature struct {
R, S *big.Int
}
func sign(alg SignatureScheme, privateKey crypto.Signer, sigInput []byte) ([]byte, error) {
var opts crypto.SignerOpts
hash := hashMap[alg]
if hash == crypto.SHA1 {
return nil, fmt.Errorf("tls.crypt.sign: Use of SHA-1 is forbidden")
}
sigType := sigMap[alg]
var realInput []byte
switch key := privateKey.(type) {
case *rsa.PrivateKey:
switch {
case allowPKCS1 && sigType == signatureAlgorithmRSA_PKCS1:
logf(logTypeCrypto, "signing with PKCS1, hashSize=[%d]", hash.Size())
opts = hash
case !allowPKCS1 && sigType == signatureAlgorithmRSA_PKCS1:
fallthrough
case sigType == signatureAlgorithmRSA_PSS:
logf(logTypeCrypto, "signing with PSS, hashSize=[%d]", hash.Size())
opts = &rsa.PSSOptions{SaltLength: hash.Size(), Hash: hash}
default:
return nil, fmt.Errorf("tls.crypto.sign: Unsupported algorithm for RSA key")
}
h := hash.New()
h.Write(sigInput)
realInput = h.Sum(nil)
case *ecdsa.PrivateKey:
if sigType != signatureAlgorithmECDSA {
return nil, fmt.Errorf("tls.crypto.sign: Unsupported algorithm for ECDSA key")
}
algGroup := curveMap[alg]
keyGroup := namedGroupFromECDSAKey(key.Public().(*ecdsa.PublicKey))
if algGroup != keyGroup {
return nil, fmt.Errorf("tls.crypto.sign: Unsupported hash/curve combination")
}
h := hash.New()
h.Write(sigInput)
realInput = h.Sum(nil)
default:
return nil, fmt.Errorf("tls.crypto.sign: Unsupported private key type")
}
sig, err := privateKey.Sign(prng, realInput, opts)
logf(logTypeCrypto, "signature: %x", sig)
return sig, err
}
func verify(alg SignatureScheme, publicKey crypto.PublicKey, sigInput []byte, sig []byte) error {
hash := hashMap[alg]
if hash == crypto.SHA1 {
return fmt.Errorf("tls.crypt.sign: Use of SHA-1 is forbidden")
}
sigType := sigMap[alg]
switch pub := publicKey.(type) {
case *rsa.PublicKey:
switch {
case allowPKCS1 && sigType == signatureAlgorithmRSA_PKCS1:
logf(logTypeCrypto, "verifying with PKCS1, hashSize=[%d]", hash.Size())
h := hash.New()
h.Write(sigInput)
realInput := h.Sum(nil)
return rsa.VerifyPKCS1v15(pub, hash, realInput, sig)
case !allowPKCS1 && sigType == signatureAlgorithmRSA_PKCS1:
fallthrough
case sigType == signatureAlgorithmRSA_PSS:
logf(logTypeCrypto, "verifying with PSS, hashSize=[%d]", hash.Size())
opts := &rsa.PSSOptions{SaltLength: hash.Size(), Hash: hash}
h := hash.New()
h.Write(sigInput)
realInput := h.Sum(nil)
return rsa.VerifyPSS(pub, hash, realInput, sig, opts)
default:
return fmt.Errorf("tls.verify: Unsupported algorithm for RSA key")
}
case *ecdsa.PublicKey:
if sigType != signatureAlgorithmECDSA {
return fmt.Errorf("tls.verify: Unsupported algorithm for ECDSA key")
}
if curveMap[alg] != namedGroupFromECDSAKey(pub) {
return fmt.Errorf("tls.verify: Unsupported curve for ECDSA key")
}
ecdsaSig := new(ecdsaSignature)
if rest, err := asn1.Unmarshal(sig, ecdsaSig); err != nil {
return err
} else if len(rest) != 0 {
return fmt.Errorf("tls.verify: trailing data after ECDSA signature")
}
if ecdsaSig.R.Sign() <= 0 || ecdsaSig.S.Sign() <= 0 {
return fmt.Errorf("tls.verify: ECDSA signature contained zero or negative values")
}
h := hash.New()
h.Write(sigInput)
realInput := h.Sum(nil)
if !ecdsa.Verify(pub, realInput, ecdsaSig.R, ecdsaSig.S) {
return fmt.Errorf("tls.verify: ECDSA verification failure")
}
return nil
default:
return fmt.Errorf("tls.verify: Unsupported key type")
}
}
// 0
// |
// v
// PSK -> HKDF-Extract = Early Secret
// |
// +-----> Derive-Secret(.,
// | "ext binder" |
// | "res binder",
// | "")
// | = binder_key
// |
// +-----> Derive-Secret(., "c e traffic",
// | ClientHello)
// | = client_early_traffic_secret
// |
// +-----> Derive-Secret(., "e exp master",
// | ClientHello)
// | = early_exporter_master_secret
// v
// Derive-Secret(., "derived", "")
// |
// v
// (EC)DHE -> HKDF-Extract = Handshake Secret
// |
// +-----> Derive-Secret(., "c hs traffic",
// | ClientHello...ServerHello)
// | = client_handshake_traffic_secret
// |
// +-----> Derive-Secret(., "s hs traffic",
// | ClientHello...ServerHello)
// | = server_handshake_traffic_secret
// v
// Derive-Secret(., "derived", "")
// |
// v
// 0 -> HKDF-Extract = Master Secret
// |
// +-----> Derive-Secret(., "c ap traffic",
// | ClientHello...server Finished)
// | = client_application_traffic_secret_0
// |
// +-----> Derive-Secret(., "s ap traffic",
// | ClientHello...server Finished)
// | = server_application_traffic_secret_0
// |
// +-----> Derive-Secret(., "exp master",
// | ClientHello...server Finished)
// | = exporter_master_secret
// |
// +-----> Derive-Secret(., "res master",
// ClientHello...client Finished)
// = resumption_master_secret
// From RFC 5869
// PRK = HMAC-Hash(salt, IKM)
func HkdfExtract(hash crypto.Hash, saltIn, input []byte) []byte {
salt := saltIn
// if [salt is] not provided, it is set to a string of HashLen zeros
if salt == nil {
salt = bytes.Repeat([]byte{0}, hash.Size())
}
h := hmac.New(hash.New, salt)
h.Write(input)
out := h.Sum(nil)
logf(logTypeCrypto, "HKDF Extract:\n")
logf(logTypeCrypto, "Salt [%d]: %x\n", len(salt), salt)
logf(logTypeCrypto, "Input [%d]: %x\n", len(input), input)
logf(logTypeCrypto, "Output [%d]: %x\n", len(out), out)
return out
}
const (
labelExternalBinder = "ext binder"
labelResumptionBinder = "res binder"
labelEarlyTrafficSecret = "c e traffic"
labelEarlyExporterSecret = "e exp master"
labelClientHandshakeTrafficSecret = "c hs traffic"
labelServerHandshakeTrafficSecret = "s hs traffic"
labelClientApplicationTrafficSecret = "c ap traffic"
labelServerApplicationTrafficSecret = "s ap traffic"
labelExporterSecret = "exp master"
labelResumptionSecret = "res master"
labelDerived = "derived"
labelFinished = "finished"
labelResumption = "resumption"
)
// struct HkdfLabel {
// uint16 length;
// opaque label<9..255>;
// opaque hash_value<0..255>;
// };
func hkdfEncodeLabel(labelIn string, hashValue []byte, outLen int) []byte {
label := "tls13 " + labelIn
labelLen := len(label)
hashLen := len(hashValue)
hkdfLabel := make([]byte, 2+1+labelLen+1+hashLen)
hkdfLabel[0] = byte(outLen >> 8)
hkdfLabel[1] = byte(outLen)
hkdfLabel[2] = byte(labelLen)
copy(hkdfLabel[3:3+labelLen], []byte(label))
hkdfLabel[3+labelLen] = byte(hashLen)
copy(hkdfLabel[3+labelLen+1:], hashValue)
return hkdfLabel
}
func HkdfExpand(hash crypto.Hash, prk, info []byte, outLen int) []byte {
out := []byte{}
T := []byte{}
i := byte(1)
for len(out) < outLen {
block := append(T, info...)
block = append(block, i)
h := hmac.New(hash.New, prk)
h.Write(block)
T = h.Sum(nil)
out = append(out, T...)
i++
}
return out[:outLen]
}
func HkdfExpandLabel(hash crypto.Hash, secret []byte, label string, hashValue []byte, outLen int) []byte {
info := hkdfEncodeLabel(label, hashValue, outLen)
derived := HkdfExpand(hash, secret, info, outLen)
logf(logTypeCrypto, "HKDF Expand: label=[tls13 ] + '%s',requested length=%d\n", label, outLen)
logf(logTypeCrypto, "PRK [%d]: %x\n", len(secret), secret)
logf(logTypeCrypto, "Hash [%d]: %x\n", len(hashValue), hashValue)
logf(logTypeCrypto, "Info [%d]: %x\n", len(info), info)
logf(logTypeCrypto, "Derived key [%d]: %x\n", len(derived), derived)
return derived
}
func deriveSecret(params CipherSuiteParams, secret []byte, label string, messageHash []byte) []byte {
return HkdfExpandLabel(params.Hash, secret, label, messageHash, params.Hash.Size())
}
func computeFinishedData(params CipherSuiteParams, baseKey []byte, input []byte) []byte {
macKey := HkdfExpandLabel(params.Hash, baseKey, labelFinished, []byte{}, params.Hash.Size())
mac := hmac.New(params.Hash.New, macKey)
mac.Write(input)
return mac.Sum(nil)
}
type keySet struct {
cipher aeadFactory
key []byte
iv []byte
}
func makeTrafficKeys(params CipherSuiteParams, secret []byte) keySet {
logf(logTypeCrypto, "making traffic keys: secret=%x", secret)
return keySet{
cipher: params.Cipher,
key: HkdfExpandLabel(params.Hash, secret, "key", []byte{}, params.KeyLen),
iv: HkdfExpandLabel(params.Hash, secret, "iv", []byte{}, params.IvLen),
}
}

View File

@ -1,586 +0,0 @@
package mint
import (
"bytes"
"fmt"
"github.com/bifurcation/mint/syntax"
)
type ExtensionBody interface {
Type() ExtensionType
Marshal() ([]byte, error)
Unmarshal(data []byte) (int, error)
}
// struct {
// ExtensionType extension_type;
// opaque extension_data<0..2^16-1>;
// } Extension;
type Extension struct {
ExtensionType ExtensionType
ExtensionData []byte `tls:"head=2"`
}
func (ext Extension) Marshal() ([]byte, error) {
return syntax.Marshal(ext)
}
func (ext *Extension) Unmarshal(data []byte) (int, error) {
return syntax.Unmarshal(data, ext)
}
type ExtensionList []Extension
type extensionListInner struct {
List []Extension `tls:"head=2"`
}
func (el ExtensionList) Marshal() ([]byte, error) {
return syntax.Marshal(extensionListInner{el})
}
func (el *ExtensionList) Unmarshal(data []byte) (int, error) {
var list extensionListInner
read, err := syntax.Unmarshal(data, &list)
if err != nil {
return 0, err
}
*el = list.List
return read, nil
}
func (el *ExtensionList) Add(src ExtensionBody) error {
data, err := src.Marshal()
if err != nil {
return err
}
if el == nil {
el = new(ExtensionList)
}
// If one already exists with this type, replace it
for i := range *el {
if (*el)[i].ExtensionType == src.Type() {
(*el)[i].ExtensionData = data
return nil
}
}
// Otherwise append
*el = append(*el, Extension{
ExtensionType: src.Type(),
ExtensionData: data,
})
return nil
}
func (el ExtensionList) Find(dst ExtensionBody) bool {
for _, ext := range el {
if ext.ExtensionType == dst.Type() {
_, err := dst.Unmarshal(ext.ExtensionData)
return err == nil
}
}
return false
}
// struct {
// NameType name_type;
// select (name_type) {
// case host_name: HostName;
// } name;
// } ServerName;
//
// enum {
// host_name(0), (255)
// } NameType;
//
// opaque HostName<1..2^16-1>;
//
// struct {
// ServerName server_name_list<1..2^16-1>
// } ServerNameList;
//
// But we only care about the case where there's a single DNS hostname. We
// will never create anything else, and throw if we receive something else
//
// 2 1 2
// | listLen | NameType | nameLen | name |
type ServerNameExtension string
type serverNameInner struct {
NameType uint8
HostName []byte `tls:"head=2,min=1"`
}
type serverNameListInner struct {
ServerNameList []serverNameInner `tls:"head=2,min=1"`
}
func (sni ServerNameExtension) Type() ExtensionType {
return ExtensionTypeServerName
}
func (sni ServerNameExtension) Marshal() ([]byte, error) {
list := serverNameListInner{
ServerNameList: []serverNameInner{{
NameType: 0x00, // host_name
HostName: []byte(sni),
}},
}
return syntax.Marshal(list)
}
func (sni *ServerNameExtension) Unmarshal(data []byte) (int, error) {
var list serverNameListInner
read, err := syntax.Unmarshal(data, &list)
if err != nil {
return 0, err
}
// Syntax requires at least one entry
// Entries beyond the first are ignored
if nameType := list.ServerNameList[0].NameType; nameType != 0x00 {
return 0, fmt.Errorf("tls.servername: Unsupported name type [%x]", nameType)
}
*sni = ServerNameExtension(list.ServerNameList[0].HostName)
return read, nil
}
// struct {
// NamedGroup group;
// opaque key_exchange<1..2^16-1>;
// } KeyShareEntry;
//
// struct {
// select (Handshake.msg_type) {
// case client_hello:
// KeyShareEntry client_shares<0..2^16-1>;
//
// case hello_retry_request:
// NamedGroup selected_group;
//
// case server_hello:
// KeyShareEntry server_share;
// };
// } KeyShare;
type KeyShareEntry struct {
Group NamedGroup
KeyExchange []byte `tls:"head=2,min=1"`
}
func (kse KeyShareEntry) SizeValid() bool {
return len(kse.KeyExchange) == keyExchangeSizeFromNamedGroup(kse.Group)
}
type KeyShareExtension struct {
HandshakeType HandshakeType
SelectedGroup NamedGroup
Shares []KeyShareEntry
}
type KeyShareClientHelloInner struct {
ClientShares []KeyShareEntry `tls:"head=2,min=0"`
}
type KeyShareHelloRetryInner struct {
SelectedGroup NamedGroup
}
type KeyShareServerHelloInner struct {
ServerShare KeyShareEntry
}
func (ks KeyShareExtension) Type() ExtensionType {
return ExtensionTypeKeyShare
}
func (ks KeyShareExtension) Marshal() ([]byte, error) {
switch ks.HandshakeType {
case HandshakeTypeClientHello:
for _, share := range ks.Shares {
if !share.SizeValid() {
return nil, fmt.Errorf("tls.keyshare: Key share has wrong size for group")
}
}
return syntax.Marshal(KeyShareClientHelloInner{ks.Shares})
case HandshakeTypeHelloRetryRequest:
if len(ks.Shares) > 0 {
return nil, fmt.Errorf("tls.keyshare: Key shares not allowed for HelloRetryRequest")
}
return syntax.Marshal(KeyShareHelloRetryInner{ks.SelectedGroup})
case HandshakeTypeServerHello:
if len(ks.Shares) != 1 {
return nil, fmt.Errorf("tls.keyshare: Server must send exactly one key share")
}
if !ks.Shares[0].SizeValid() {
return nil, fmt.Errorf("tls.keyshare: Key share has wrong size for group")
}
return syntax.Marshal(KeyShareServerHelloInner{ks.Shares[0]})
default:
return nil, fmt.Errorf("tls.keyshare: Handshake type not allowed")
}
}
func (ks *KeyShareExtension) Unmarshal(data []byte) (int, error) {
switch ks.HandshakeType {
case HandshakeTypeClientHello:
var inner KeyShareClientHelloInner
read, err := syntax.Unmarshal(data, &inner)
if err != nil {
return 0, err
}
for _, share := range inner.ClientShares {
if !share.SizeValid() {
return 0, fmt.Errorf("tls.keyshare: Key share has wrong size for group")
}
}
ks.Shares = inner.ClientShares
return read, nil
case HandshakeTypeHelloRetryRequest:
var inner KeyShareHelloRetryInner
read, err := syntax.Unmarshal(data, &inner)
if err != nil {
return 0, err
}
ks.SelectedGroup = inner.SelectedGroup
return read, nil
case HandshakeTypeServerHello:
var inner KeyShareServerHelloInner
read, err := syntax.Unmarshal(data, &inner)
if err != nil {
return 0, err
}
if !inner.ServerShare.SizeValid() {
return 0, fmt.Errorf("tls.keyshare: Key share has wrong size for group")
}
ks.Shares = []KeyShareEntry{inner.ServerShare}
return read, nil
default:
return 0, fmt.Errorf("tls.keyshare: Handshake type not allowed")
}
}
// struct {
// NamedGroup named_group_list<2..2^16-1>;
// } NamedGroupList;
type SupportedGroupsExtension struct {
Groups []NamedGroup `tls:"head=2,min=2"`
}
func (sg SupportedGroupsExtension) Type() ExtensionType {
return ExtensionTypeSupportedGroups
}
func (sg SupportedGroupsExtension) Marshal() ([]byte, error) {
return syntax.Marshal(sg)
}
func (sg *SupportedGroupsExtension) Unmarshal(data []byte) (int, error) {
return syntax.Unmarshal(data, sg)
}
// struct {
// SignatureScheme supported_signature_algorithms<2..2^16-2>;
// } SignatureSchemeList
type SignatureAlgorithmsExtension struct {
Algorithms []SignatureScheme `tls:"head=2,min=2"`
}
func (sa SignatureAlgorithmsExtension) Type() ExtensionType {
return ExtensionTypeSignatureAlgorithms
}
func (sa SignatureAlgorithmsExtension) Marshal() ([]byte, error) {
return syntax.Marshal(sa)
}
func (sa *SignatureAlgorithmsExtension) Unmarshal(data []byte) (int, error) {
return syntax.Unmarshal(data, sa)
}
// struct {
// opaque identity<1..2^16-1>;
// uint32 obfuscated_ticket_age;
// } PskIdentity;
//
// opaque PskBinderEntry<32..255>;
//
// struct {
// select (Handshake.msg_type) {
// case client_hello:
// PskIdentity identities<7..2^16-1>;
// PskBinderEntry binders<33..2^16-1>;
//
// case server_hello:
// uint16 selected_identity;
// };
//
// } PreSharedKeyExtension;
type PSKIdentity struct {
Identity []byte `tls:"head=2,min=1"`
ObfuscatedTicketAge uint32
}
type PSKBinderEntry struct {
Binder []byte `tls:"head=1,min=32"`
}
type PreSharedKeyExtension struct {
HandshakeType HandshakeType
Identities []PSKIdentity
Binders []PSKBinderEntry
SelectedIdentity uint16
}
type preSharedKeyClientInner struct {
Identities []PSKIdentity `tls:"head=2,min=7"`
Binders []PSKBinderEntry `tls:"head=2,min=33"`
}
type preSharedKeyServerInner struct {
SelectedIdentity uint16
}
func (psk PreSharedKeyExtension) Type() ExtensionType {
return ExtensionTypePreSharedKey
}
func (psk PreSharedKeyExtension) Marshal() ([]byte, error) {
switch psk.HandshakeType {
case HandshakeTypeClientHello:
return syntax.Marshal(preSharedKeyClientInner{
Identities: psk.Identities,
Binders: psk.Binders,
})
case HandshakeTypeServerHello:
if len(psk.Identities) > 0 || len(psk.Binders) > 0 {
return nil, fmt.Errorf("tls.presharedkey: Server can only provide an index")
}
return syntax.Marshal(preSharedKeyServerInner{psk.SelectedIdentity})
default:
return nil, fmt.Errorf("tls.presharedkey: Handshake type not supported")
}
}
func (psk *PreSharedKeyExtension) Unmarshal(data []byte) (int, error) {
switch psk.HandshakeType {
case HandshakeTypeClientHello:
var inner preSharedKeyClientInner
read, err := syntax.Unmarshal(data, &inner)
if err != nil {
return 0, err
}
if len(inner.Identities) != len(inner.Binders) {
return 0, fmt.Errorf("Lengths of identities and binders not equal")
}
psk.Identities = inner.Identities
psk.Binders = inner.Binders
return read, nil
case HandshakeTypeServerHello:
var inner preSharedKeyServerInner
read, err := syntax.Unmarshal(data, &inner)
if err != nil {
return 0, err
}
psk.SelectedIdentity = inner.SelectedIdentity
return read, nil
default:
return 0, fmt.Errorf("tls.presharedkey: Handshake type not supported")
}
}
func (psk PreSharedKeyExtension) HasIdentity(id []byte) ([]byte, bool) {
for i, localID := range psk.Identities {
if bytes.Equal(localID.Identity, id) {
return psk.Binders[i].Binder, true
}
}
return nil, false
}
// enum { psk_ke(0), psk_dhe_ke(1), (255) } PskKeyExchangeMode;
//
// struct {
// PskKeyExchangeMode ke_modes<1..255>;
// } PskKeyExchangeModes;
type PSKKeyExchangeModesExtension struct {
KEModes []PSKKeyExchangeMode `tls:"head=1,min=1"`
}
func (pkem PSKKeyExchangeModesExtension) Type() ExtensionType {
return ExtensionTypePSKKeyExchangeModes
}
func (pkem PSKKeyExchangeModesExtension) Marshal() ([]byte, error) {
return syntax.Marshal(pkem)
}
func (pkem *PSKKeyExchangeModesExtension) Unmarshal(data []byte) (int, error) {
return syntax.Unmarshal(data, pkem)
}
// struct {
// } EarlyDataIndication;
type EarlyDataExtension struct{}
func (ed EarlyDataExtension) Type() ExtensionType {
return ExtensionTypeEarlyData
}
func (ed EarlyDataExtension) Marshal() ([]byte, error) {
return []byte{}, nil
}
func (ed *EarlyDataExtension) Unmarshal(data []byte) (int, error) {
return 0, nil
}
// struct {
// uint32 max_early_data_size;
// } TicketEarlyDataInfo;
type TicketEarlyDataInfoExtension struct {
MaxEarlyDataSize uint32
}
func (tedi TicketEarlyDataInfoExtension) Type() ExtensionType {
return ExtensionTypeTicketEarlyDataInfo
}
func (tedi TicketEarlyDataInfoExtension) Marshal() ([]byte, error) {
return syntax.Marshal(tedi)
}
func (tedi *TicketEarlyDataInfoExtension) Unmarshal(data []byte) (int, error) {
return syntax.Unmarshal(data, tedi)
}
// opaque ProtocolName<1..2^8-1>;
//
// struct {
// ProtocolName protocol_name_list<2..2^16-1>
// } ProtocolNameList;
type ALPNExtension struct {
Protocols []string
}
type protocolNameInner struct {
Name []byte `tls:"head=1,min=1"`
}
type alpnExtensionInner struct {
Protocols []protocolNameInner `tls:"head=2,min=2"`
}
func (alpn ALPNExtension) Type() ExtensionType {
return ExtensionTypeALPN
}
func (alpn ALPNExtension) Marshal() ([]byte, error) {
protocols := make([]protocolNameInner, len(alpn.Protocols))
for i, protocol := range alpn.Protocols {
protocols[i] = protocolNameInner{[]byte(protocol)}
}
return syntax.Marshal(alpnExtensionInner{protocols})
}
func (alpn *ALPNExtension) Unmarshal(data []byte) (int, error) {
var inner alpnExtensionInner
read, err := syntax.Unmarshal(data, &inner)
if err != nil {
return 0, err
}
alpn.Protocols = make([]string, len(inner.Protocols))
for i, protocol := range inner.Protocols {
alpn.Protocols[i] = string(protocol.Name)
}
return read, nil
}
// struct {
// ProtocolVersion versions<2..254>;
// } SupportedVersions;
type SupportedVersionsExtension struct {
Versions []uint16 `tls:"head=1,min=2,max=254"`
}
func (sv SupportedVersionsExtension) Type() ExtensionType {
return ExtensionTypeSupportedVersions
}
func (sv SupportedVersionsExtension) Marshal() ([]byte, error) {
return syntax.Marshal(sv)
}
func (sv *SupportedVersionsExtension) Unmarshal(data []byte) (int, error) {
return syntax.Unmarshal(data, sv)
}
// struct {
// opaque cookie<1..2^16-1>;
// } Cookie;
type CookieExtension struct {
Cookie []byte `tls:"head=2,min=1"`
}
func (c CookieExtension) Type() ExtensionType {
return ExtensionTypeCookie
}
func (c CookieExtension) Marshal() ([]byte, error) {
return syntax.Marshal(c)
}
func (c *CookieExtension) Unmarshal(data []byte) (int, error) {
return syntax.Unmarshal(data, c)
}
// defaultCookieLength is the default length of a cookie
const defaultCookieLength = 32
type defaultCookieHandler struct {
data []byte
}
var _ CookieHandler = &defaultCookieHandler{}
// NewRandomCookie generates a cookie with DefaultCookieLength bytes of random data
func (h *defaultCookieHandler) Generate(*Conn) ([]byte, error) {
h.data = make([]byte, defaultCookieLength)
if _, err := prng.Read(h.data); err != nil {
return nil, err
}
return h.data, nil
}
func (h *defaultCookieHandler) Validate(_ *Conn, data []byte) bool {
return bytes.Equal(h.data, data)
}

View File

@ -1,147 +0,0 @@
package mint
import (
"encoding/hex"
"math/big"
)
var (
finiteFieldPrime2048hex = "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1" +
"D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" +
"7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561" +
"2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" +
"984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735" +
"30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" +
"B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19" +
"0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" +
"9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73" +
"3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" +
"886B423861285C97FFFFFFFFFFFFFFFF"
finiteFieldPrime2048bytes, _ = hex.DecodeString(finiteFieldPrime2048hex)
finiteFieldPrime2048 = big.NewInt(0).SetBytes(finiteFieldPrime2048bytes)
finiteFieldPrime3072hex = "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1" +
"D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" +
"7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561" +
"2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" +
"984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735" +
"30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" +
"B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19" +
"0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" +
"9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73" +
"3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" +
"886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238" +
"61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C" +
"AEFE130985139270B4130C93BC437944F4FD4452E2D74DD3" +
"64F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0D" +
"ABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF" +
"3C1B20EE3FD59D7C25E41D2B66C62E37FFFFFFFFFFFFFFFF"
finiteFieldPrime3072bytes, _ = hex.DecodeString(finiteFieldPrime3072hex)
finiteFieldPrime3072 = big.NewInt(0).SetBytes(finiteFieldPrime3072bytes)
finiteFieldPrime4096hex = "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1" +
"D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" +
"7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561" +
"2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" +
"984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735" +
"30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" +
"B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19" +
"0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" +
"9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73" +
"3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" +
"886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238" +
"61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C" +
"AEFE130985139270B4130C93BC437944F4FD4452E2D74DD3" +
"64F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0D" +
"ABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF" +
"3C1B20EE3FD59D7C25E41D2B669E1EF16E6F52C3164DF4FB" +
"7930E9E4E58857B6AC7D5F42D69F6D187763CF1D55034004" +
"87F55BA57E31CC7A7135C886EFB4318AED6A1E012D9E6832" +
"A907600A918130C46DC778F971AD0038092999A333CB8B7A" +
"1A1DB93D7140003C2A4ECEA9F98D0ACC0A8291CDCEC97DCF" +
"8EC9B55A7F88A46B4DB5A851F44182E1C68A007E5E655F6A" +
"FFFFFFFFFFFFFFFF"
finiteFieldPrime4096bytes, _ = hex.DecodeString(finiteFieldPrime4096hex)
finiteFieldPrime4096 = big.NewInt(0).SetBytes(finiteFieldPrime4096bytes)
finiteFieldPrime6144hex = "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1" +
"D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" +
"7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561" +
"2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" +
"984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735" +
"30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" +
"B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19" +
"0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" +
"9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73" +
"3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" +
"886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238" +
"61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C" +
"AEFE130985139270B4130C93BC437944F4FD4452E2D74DD3" +
"64F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0D" +
"ABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF" +
"3C1B20EE3FD59D7C25E41D2B669E1EF16E6F52C3164DF4FB" +
"7930E9E4E58857B6AC7D5F42D69F6D187763CF1D55034004" +
"87F55BA57E31CC7A7135C886EFB4318AED6A1E012D9E6832" +
"A907600A918130C46DC778F971AD0038092999A333CB8B7A" +
"1A1DB93D7140003C2A4ECEA9F98D0ACC0A8291CDCEC97DCF" +
"8EC9B55A7F88A46B4DB5A851F44182E1C68A007E5E0DD902" +
"0BFD64B645036C7A4E677D2C38532A3A23BA4442CAF53EA6" +
"3BB454329B7624C8917BDD64B1C0FD4CB38E8C334C701C3A" +
"CDAD0657FCCFEC719B1F5C3E4E46041F388147FB4CFDB477" +
"A52471F7A9A96910B855322EDB6340D8A00EF092350511E3" +
"0ABEC1FFF9E3A26E7FB29F8C183023C3587E38DA0077D9B4" +
"763E4E4B94B2BBC194C6651E77CAF992EEAAC0232A281BF6" +
"B3A739C1226116820AE8DB5847A67CBEF9C9091B462D538C" +
"D72B03746AE77F5E62292C311562A846505DC82DB854338A" +
"E49F5235C95B91178CCF2DD5CACEF403EC9D1810C6272B04" +
"5B3B71F9DC6B80D63FDD4A8E9ADB1E6962A69526D43161C1" +
"A41D570D7938DAD4A40E329CD0E40E65FFFFFFFFFFFFFFFF"
finiteFieldPrime6144bytes, _ = hex.DecodeString(finiteFieldPrime6144hex)
finiteFieldPrime6144 = big.NewInt(0).SetBytes(finiteFieldPrime6144bytes)
finiteFieldPrime8192hex = "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1" +
"D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" +
"7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561" +
"2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" +
"984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735" +
"30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" +
"B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19" +
"0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" +
"9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73" +
"3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" +
"886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238" +
"61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C" +
"AEFE130985139270B4130C93BC437944F4FD4452E2D74DD3" +
"64F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0D" +
"ABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF" +
"3C1B20EE3FD59D7C25E41D2B669E1EF16E6F52C3164DF4FB" +
"7930E9E4E58857B6AC7D5F42D69F6D187763CF1D55034004" +
"87F55BA57E31CC7A7135C886EFB4318AED6A1E012D9E6832" +
"A907600A918130C46DC778F971AD0038092999A333CB8B7A" +
"1A1DB93D7140003C2A4ECEA9F98D0ACC0A8291CDCEC97DCF" +
"8EC9B55A7F88A46B4DB5A851F44182E1C68A007E5E0DD902" +
"0BFD64B645036C7A4E677D2C38532A3A23BA4442CAF53EA6" +
"3BB454329B7624C8917BDD64B1C0FD4CB38E8C334C701C3A" +
"CDAD0657FCCFEC719B1F5C3E4E46041F388147FB4CFDB477" +
"A52471F7A9A96910B855322EDB6340D8A00EF092350511E3" +
"0ABEC1FFF9E3A26E7FB29F8C183023C3587E38DA0077D9B4" +
"763E4E4B94B2BBC194C6651E77CAF992EEAAC0232A281BF6" +
"B3A739C1226116820AE8DB5847A67CBEF9C9091B462D538C" +
"D72B03746AE77F5E62292C311562A846505DC82DB854338A" +
"E49F5235C95B91178CCF2DD5CACEF403EC9D1810C6272B04" +
"5B3B71F9DC6B80D63FDD4A8E9ADB1E6962A69526D43161C1" +
"A41D570D7938DAD4A40E329CCFF46AAA36AD004CF600C838" +
"1E425A31D951AE64FDB23FCEC9509D43687FEB69EDD1CC5E" +
"0B8CC3BDF64B10EF86B63142A3AB8829555B2F747C932665" +
"CB2C0F1CC01BD70229388839D2AF05E454504AC78B758282" +
"2846C0BA35C35F5C59160CC046FD8251541FC68C9C86B022" +
"BB7099876A460E7451A8A93109703FEE1C217E6C3826E52C" +
"51AA691E0E423CFC99E9E31650C1217B624816CDAD9A95F9" +
"D5B8019488D9C0A0A1FE3075A577E23183F81D4A3F2FA457" +
"1EFC8CE0BA8A4FE8B6855DFE72B0A66EDED2FBABFBE58A30" +
"FAFABE1C5D71A87E2F741EF8C1FE86FEA6BBFDE530677F0D" +
"97D11D49F7A8443D0822E506A9F4614E011E2A94838FF88C" +
"D68C8BB7C5C6424CFFFFFFFFFFFFFFFF"
finiteFieldPrime8192bytes, _ = hex.DecodeString(finiteFieldPrime8192hex)
finiteFieldPrime8192 = big.NewInt(0).SetBytes(finiteFieldPrime8192bytes)
)

View File

@ -1,98 +0,0 @@
// Read a generic "framed" packet consisting of a header and a
// This is used for both TLS Records and TLS Handshake Messages
package mint
type framing interface {
headerLen() int
defaultReadLen() int
frameLen(hdr []byte) (int, error)
}
const (
kFrameReaderHdr = 0
kFrameReaderBody = 1
)
type frameNextAction func(f *frameReader) error
type frameReader struct {
details framing
state uint8
header []byte
body []byte
working []byte
writeOffset int
remainder []byte
}
func newFrameReader(d framing) *frameReader {
hdr := make([]byte, d.headerLen())
return &frameReader{
d,
kFrameReaderHdr,
hdr,
nil,
hdr,
0,
nil,
}
}
func dup(a []byte) []byte {
r := make([]byte, len(a))
copy(r, a)
return r
}
func (f *frameReader) needed() int {
tmp := (len(f.working) - f.writeOffset) - len(f.remainder)
if tmp < 0 {
return 0
}
return tmp
}
func (f *frameReader) addChunk(in []byte) {
// Append to the buffer.
logf(logTypeFrameReader, "Appending %v", len(in))
f.remainder = append(f.remainder, in...)
}
func (f *frameReader) process() (hdr []byte, body []byte, err error) {
for f.needed() == 0 {
logf(logTypeFrameReader, "%v bytes needed for next block", len(f.working)-f.writeOffset)
// Fill out our working block
copied := copy(f.working[f.writeOffset:], f.remainder)
f.remainder = f.remainder[copied:]
f.writeOffset += copied
if f.writeOffset < len(f.working) {
logf(logTypeFrameReader, "Read would have blocked 1")
return nil, nil, WouldBlock
}
// Reset the write offset, because we are now full.
f.writeOffset = 0
// We have read a full frame
if f.state == kFrameReaderBody {
logf(logTypeFrameReader, "Returning frame hdr=%#x len=%d buffered=%d", f.header, len(f.body), len(f.remainder))
f.state = kFrameReaderHdr
f.working = f.header
return dup(f.header), dup(f.body), nil
}
// We have read the header
bodyLen, err := f.details.frameLen(f.header)
if err != nil {
return nil, nil, err
}
logf(logTypeFrameReader, "Processed header, body len = %v", bodyLen)
f.body = make([]byte, bodyLen)
f.working = f.body
f.writeOffset = 0
f.state = kFrameReaderBody
}
logf(logTypeFrameReader, "Read would have blocked 2")
return nil, nil, WouldBlock
}

View File

@ -1,253 +0,0 @@
package mint
import (
"fmt"
"io"
"net"
)
const (
handshakeHeaderLen = 4 // handshake message header length
maxHandshakeMessageLen = 1 << 24 // max handshake message length
)
// struct {
// HandshakeType msg_type; /* handshake type */
// uint24 length; /* bytes in message */
// select (HandshakeType) {
// ...
// } body;
// } Handshake;
//
// We do the select{...} part in a different layer, so we treat the
// actual message body as opaque:
//
// struct {
// HandshakeType msg_type;
// opaque msg<0..2^24-1>
// } Handshake;
//
// TODO: File a spec bug
type HandshakeMessage struct {
// Omitted: length
msgType HandshakeType
body []byte
}
// Note: This could be done with the `syntax` module, using the simplified
// syntax as discussed above. However, since this is so simple, there's not
// much benefit to doing so.
func (hm *HandshakeMessage) Marshal() []byte {
if hm == nil {
return []byte{}
}
msgLen := len(hm.body)
data := make([]byte, 4+len(hm.body))
data[0] = byte(hm.msgType)
data[1] = byte(msgLen >> 16)
data[2] = byte(msgLen >> 8)
data[3] = byte(msgLen)
copy(data[4:], hm.body)
return data
}
func (hm HandshakeMessage) ToBody() (HandshakeMessageBody, error) {
logf(logTypeHandshake, "HandshakeMessage.toBody [%d] [%x]", hm.msgType, hm.body)
var body HandshakeMessageBody
switch hm.msgType {
case HandshakeTypeClientHello:
body = new(ClientHelloBody)
case HandshakeTypeServerHello:
body = new(ServerHelloBody)
case HandshakeTypeHelloRetryRequest:
body = new(HelloRetryRequestBody)
case HandshakeTypeEncryptedExtensions:
body = new(EncryptedExtensionsBody)
case HandshakeTypeCertificate:
body = new(CertificateBody)
case HandshakeTypeCertificateRequest:
body = new(CertificateRequestBody)
case HandshakeTypeCertificateVerify:
body = new(CertificateVerifyBody)
case HandshakeTypeFinished:
body = &FinishedBody{VerifyDataLen: len(hm.body)}
case HandshakeTypeNewSessionTicket:
body = new(NewSessionTicketBody)
case HandshakeTypeKeyUpdate:
body = new(KeyUpdateBody)
case HandshakeTypeEndOfEarlyData:
body = new(EndOfEarlyDataBody)
default:
return body, fmt.Errorf("tls.handshakemessage: Unsupported body type")
}
_, err := body.Unmarshal(hm.body)
return body, err
}
func HandshakeMessageFromBody(body HandshakeMessageBody) (*HandshakeMessage, error) {
data, err := body.Marshal()
if err != nil {
return nil, err
}
return &HandshakeMessage{
msgType: body.Type(),
body: data,
}, nil
}
type HandshakeLayer struct {
nonblocking bool // Should we operate in nonblocking mode
conn *RecordLayer // Used for reading/writing records
frame *frameReader // The buffered frame reader
}
type handshakeLayerFrameDetails struct{}
func (d handshakeLayerFrameDetails) headerLen() int {
return handshakeHeaderLen
}
func (d handshakeLayerFrameDetails) defaultReadLen() int {
return handshakeHeaderLen + maxFragmentLen
}
func (d handshakeLayerFrameDetails) frameLen(hdr []byte) (int, error) {
logf(logTypeIO, "Header=%x", hdr)
return (int(hdr[1]) << 16) | (int(hdr[2]) << 8) | int(hdr[3]), nil
}
func NewHandshakeLayer(r *RecordLayer) *HandshakeLayer {
h := HandshakeLayer{}
h.conn = r
h.frame = newFrameReader(&handshakeLayerFrameDetails{})
return &h
}
func (h *HandshakeLayer) readRecord() error {
logf(logTypeIO, "Trying to read record")
pt, err := h.conn.ReadRecord()
if err != nil {
return err
}
if pt.contentType != RecordTypeHandshake &&
pt.contentType != RecordTypeAlert {
return fmt.Errorf("tls.handshakelayer: Unexpected record type %d", pt.contentType)
}
if pt.contentType == RecordTypeAlert {
logf(logTypeIO, "read alert %v", pt.fragment[1])
if len(pt.fragment) < 2 {
h.sendAlert(AlertUnexpectedMessage)
return io.EOF
}
return Alert(pt.fragment[1])
}
logf(logTypeIO, "read handshake record of len %v", len(pt.fragment))
h.frame.addChunk(pt.fragment)
return nil
}
// sendAlert sends a TLS alert message.
func (h *HandshakeLayer) sendAlert(err Alert) error {
tmp := make([]byte, 2)
tmp[0] = AlertLevelError
tmp[1] = byte(err)
h.conn.WriteRecord(&TLSPlaintext{
contentType: RecordTypeAlert,
fragment: tmp},
)
// closeNotify is a special case in that it isn't an error:
if err != AlertCloseNotify {
return &net.OpError{Op: "local error", Err: err}
}
return nil
}
func (h *HandshakeLayer) ReadMessage() (*HandshakeMessage, error) {
var hdr, body []byte
var err error
for {
logf(logTypeHandshake, "ReadMessage() buffered=%v", len(h.frame.remainder))
if h.frame.needed() > 0 {
logf(logTypeHandshake, "Trying to read a new record")
err = h.readRecord()
}
if err != nil && (h.nonblocking || err != WouldBlock) {
return nil, err
}
hdr, body, err = h.frame.process()
if err == nil {
break
}
if err != nil && (h.nonblocking || err != WouldBlock) {
return nil, err
}
}
logf(logTypeHandshake, "read handshake message")
hm := &HandshakeMessage{}
hm.msgType = HandshakeType(hdr[0])
hm.body = make([]byte, len(body))
copy(hm.body, body)
return hm, nil
}
func (h *HandshakeLayer) WriteMessage(hm *HandshakeMessage) error {
return h.WriteMessages([]*HandshakeMessage{hm})
}
func (h *HandshakeLayer) WriteMessages(hms []*HandshakeMessage) error {
for _, hm := range hms {
logf(logTypeHandshake, "WriteMessage [%d] %x", hm.msgType, hm.body)
}
// Write out headers and bodies
buffer := []byte{}
for _, msg := range hms {
msgLen := len(msg.body)
if msgLen > maxHandshakeMessageLen {
return fmt.Errorf("tls.handshakelayer: Message too large to send")
}
buffer = append(buffer, msg.Marshal()...)
}
// Send full-size fragments
var start int
for start = 0; len(buffer)-start >= maxFragmentLen; start += maxFragmentLen {
err := h.conn.WriteRecord(&TLSPlaintext{
contentType: RecordTypeHandshake,
fragment: buffer[start : start+maxFragmentLen],
})
if err != nil {
return err
}
}
// Send a final partial fragment if necessary
if start < len(buffer) {
err := h.conn.WriteRecord(&TLSPlaintext{
contentType: RecordTypeHandshake,
fragment: buffer[start:],
})
if err != nil {
return err
}
}
return nil
}

View File

@ -1,450 +0,0 @@
package mint
import (
"bytes"
"crypto"
"crypto/x509"
"encoding/binary"
"fmt"
"github.com/bifurcation/mint/syntax"
)
type HandshakeMessageBody interface {
Type() HandshakeType
Marshal() ([]byte, error)
Unmarshal(data []byte) (int, error)
}
// struct {
// ProtocolVersion legacy_version = 0x0303; /* TLS v1.2 */
// Random random;
// opaque legacy_session_id<0..32>;
// CipherSuite cipher_suites<2..2^16-2>;
// opaque legacy_compression_methods<1..2^8-1>;
// Extension extensions<0..2^16-1>;
// } ClientHello;
type ClientHelloBody struct {
// Omitted: clientVersion
// Omitted: legacySessionID
// Omitted: legacyCompressionMethods
Random [32]byte
CipherSuites []CipherSuite
Extensions ExtensionList
}
type clientHelloBodyInner struct {
LegacyVersion uint16
Random [32]byte
LegacySessionID []byte `tls:"head=1,max=32"`
CipherSuites []CipherSuite `tls:"head=2,min=2"`
LegacyCompressionMethods []byte `tls:"head=1,min=1"`
Extensions []Extension `tls:"head=2"`
}
func (ch ClientHelloBody) Type() HandshakeType {
return HandshakeTypeClientHello
}
func (ch ClientHelloBody) Marshal() ([]byte, error) {
return syntax.Marshal(clientHelloBodyInner{
LegacyVersion: 0x0303,
Random: ch.Random,
LegacySessionID: []byte{},
CipherSuites: ch.CipherSuites,
LegacyCompressionMethods: []byte{0},
Extensions: ch.Extensions,
})
}
func (ch *ClientHelloBody) Unmarshal(data []byte) (int, error) {
var inner clientHelloBodyInner
read, err := syntax.Unmarshal(data, &inner)
if err != nil {
return 0, err
}
// We are strict about these things because we only support 1.3
if inner.LegacyVersion != 0x0303 {
return 0, fmt.Errorf("tls.clienthello: Incorrect version number")
}
if len(inner.LegacyCompressionMethods) != 1 || inner.LegacyCompressionMethods[0] != 0 {
return 0, fmt.Errorf("tls.clienthello: Invalid compression method")
}
ch.Random = inner.Random
ch.CipherSuites = inner.CipherSuites
ch.Extensions = inner.Extensions
return read, nil
}
// TODO: File a spec bug to clarify this
func (ch ClientHelloBody) Truncated() ([]byte, error) {
if len(ch.Extensions) == 0 {
return nil, fmt.Errorf("tls.clienthello.truncate: No extensions")
}
pskExt := ch.Extensions[len(ch.Extensions)-1]
if pskExt.ExtensionType != ExtensionTypePreSharedKey {
return nil, fmt.Errorf("tls.clienthello.truncate: Last extension is not PSK")
}
chm, err := HandshakeMessageFromBody(&ch)
if err != nil {
return nil, err
}
chData := chm.Marshal()
psk := PreSharedKeyExtension{
HandshakeType: HandshakeTypeClientHello,
}
_, err = psk.Unmarshal(pskExt.ExtensionData)
if err != nil {
return nil, err
}
// Marshal just the binders so that we know how much to truncate
binders := struct {
Binders []PSKBinderEntry `tls:"head=2,min=33"`
}{Binders: psk.Binders}
binderData, _ := syntax.Marshal(binders)
binderLen := len(binderData)
chLen := len(chData)
return chData[:chLen-binderLen], nil
}
// struct {
// ProtocolVersion server_version;
// CipherSuite cipher_suite;
// Extension extensions<2..2^16-1>;
// } HelloRetryRequest;
type HelloRetryRequestBody struct {
Version uint16
CipherSuite CipherSuite
Extensions ExtensionList `tls:"head=2,min=2"`
}
func (hrr HelloRetryRequestBody) Type() HandshakeType {
return HandshakeTypeHelloRetryRequest
}
func (hrr HelloRetryRequestBody) Marshal() ([]byte, error) {
return syntax.Marshal(hrr)
}
func (hrr *HelloRetryRequestBody) Unmarshal(data []byte) (int, error) {
return syntax.Unmarshal(data, hrr)
}
// struct {
// ProtocolVersion version;
// Random random;
// CipherSuite cipher_suite;
// Extension extensions<0..2^16-1>;
// } ServerHello;
type ServerHelloBody struct {
Version uint16
Random [32]byte
CipherSuite CipherSuite
Extensions ExtensionList `tls:"head=2"`
}
func (sh ServerHelloBody) Type() HandshakeType {
return HandshakeTypeServerHello
}
func (sh ServerHelloBody) Marshal() ([]byte, error) {
return syntax.Marshal(sh)
}
func (sh *ServerHelloBody) Unmarshal(data []byte) (int, error) {
return syntax.Unmarshal(data, sh)
}
// struct {
// opaque verify_data[verify_data_length];
// } Finished;
//
// verifyDataLen is not a field in the TLS struct, but we add it here so
// that calling code can tell us how much data to expect when we marshal /
// unmarshal. (We could add this to the marshal/unmarshal methods, but let's
// try to keep the signature consistent for now.)
//
// For similar reasons, we don't use the `syntax` module here, because this
// struct doesn't map well to standard TLS presentation language concepts.
//
// TODO: File a spec bug
type FinishedBody struct {
VerifyDataLen int
VerifyData []byte
}
func (fin FinishedBody) Type() HandshakeType {
return HandshakeTypeFinished
}
func (fin FinishedBody) Marshal() ([]byte, error) {
if len(fin.VerifyData) != fin.VerifyDataLen {
return nil, fmt.Errorf("tls.finished: data length mismatch")
}
body := make([]byte, len(fin.VerifyData))
copy(body, fin.VerifyData)
return body, nil
}
func (fin *FinishedBody) Unmarshal(data []byte) (int, error) {
if len(data) < fin.VerifyDataLen {
return 0, fmt.Errorf("tls.finished: Malformed finished; too short")
}
fin.VerifyData = make([]byte, fin.VerifyDataLen)
copy(fin.VerifyData, data[:fin.VerifyDataLen])
return fin.VerifyDataLen, nil
}
// struct {
// Extension extensions<0..2^16-1>;
// } EncryptedExtensions;
//
// Marshal() and Unmarshal() are handled by ExtensionList
type EncryptedExtensionsBody struct {
Extensions ExtensionList `tls:"head=2"`
}
func (ee EncryptedExtensionsBody) Type() HandshakeType {
return HandshakeTypeEncryptedExtensions
}
func (ee EncryptedExtensionsBody) Marshal() ([]byte, error) {
return syntax.Marshal(ee)
}
func (ee *EncryptedExtensionsBody) Unmarshal(data []byte) (int, error) {
return syntax.Unmarshal(data, ee)
}
// opaque ASN1Cert<1..2^24-1>;
//
// struct {
// ASN1Cert cert_data;
// Extension extensions<0..2^16-1>
// } CertificateEntry;
//
// struct {
// opaque certificate_request_context<0..2^8-1>;
// CertificateEntry certificate_list<0..2^24-1>;
// } Certificate;
type CertificateEntry struct {
CertData *x509.Certificate
Extensions ExtensionList
}
type CertificateBody struct {
CertificateRequestContext []byte
CertificateList []CertificateEntry
}
type certificateEntryInner struct {
CertData []byte `tls:"head=3,min=1"`
Extensions ExtensionList `tls:"head=2"`
}
type certificateBodyInner struct {
CertificateRequestContext []byte `tls:"head=1"`
CertificateList []certificateEntryInner `tls:"head=3"`
}
func (c CertificateBody) Type() HandshakeType {
return HandshakeTypeCertificate
}
func (c CertificateBody) Marshal() ([]byte, error) {
inner := certificateBodyInner{
CertificateRequestContext: c.CertificateRequestContext,
CertificateList: make([]certificateEntryInner, len(c.CertificateList)),
}
for i, entry := range c.CertificateList {
inner.CertificateList[i] = certificateEntryInner{
CertData: entry.CertData.Raw,
Extensions: entry.Extensions,
}
}
return syntax.Marshal(inner)
}
func (c *CertificateBody) Unmarshal(data []byte) (int, error) {
inner := certificateBodyInner{}
read, err := syntax.Unmarshal(data, &inner)
if err != nil {
return read, err
}
c.CertificateRequestContext = inner.CertificateRequestContext
c.CertificateList = make([]CertificateEntry, len(inner.CertificateList))
for i, entry := range inner.CertificateList {
c.CertificateList[i].CertData, err = x509.ParseCertificate(entry.CertData)
if err != nil {
return 0, fmt.Errorf("tls:certificate: Certificate failed to parse: %v", err)
}
c.CertificateList[i].Extensions = entry.Extensions
}
return read, nil
}
// struct {
// SignatureScheme algorithm;
// opaque signature<0..2^16-1>;
// } CertificateVerify;
type CertificateVerifyBody struct {
Algorithm SignatureScheme
Signature []byte `tls:"head=2"`
}
func (cv CertificateVerifyBody) Type() HandshakeType {
return HandshakeTypeCertificateVerify
}
func (cv CertificateVerifyBody) Marshal() ([]byte, error) {
return syntax.Marshal(cv)
}
func (cv *CertificateVerifyBody) Unmarshal(data []byte) (int, error) {
return syntax.Unmarshal(data, cv)
}
func (cv *CertificateVerifyBody) EncodeSignatureInput(data []byte) []byte {
// TODO: Change context for client auth
// TODO: Put this in a const
const context = "TLS 1.3, server CertificateVerify"
sigInput := bytes.Repeat([]byte{0x20}, 64)
sigInput = append(sigInput, []byte(context)...)
sigInput = append(sigInput, []byte{0}...)
sigInput = append(sigInput, data...)
return sigInput
}
func (cv *CertificateVerifyBody) Sign(privateKey crypto.Signer, handshakeHash []byte) (err error) {
sigInput := cv.EncodeSignatureInput(handshakeHash)
cv.Signature, err = sign(cv.Algorithm, privateKey, sigInput)
logf(logTypeHandshake, "Signed: alg=[%04x] sigInput=[%x], sig=[%x]", cv.Algorithm, sigInput, cv.Signature)
return
}
func (cv *CertificateVerifyBody) Verify(publicKey crypto.PublicKey, handshakeHash []byte) error {
sigInput := cv.EncodeSignatureInput(handshakeHash)
logf(logTypeHandshake, "About to verify: alg=[%04x] sigInput=[%x], sig=[%x]", cv.Algorithm, sigInput, cv.Signature)
return verify(cv.Algorithm, publicKey, sigInput, cv.Signature)
}
// struct {
// opaque certificate_request_context<0..2^8-1>;
// Extension extensions<2..2^16-1>;
// } CertificateRequest;
type CertificateRequestBody struct {
CertificateRequestContext []byte `tls:"head=1"`
Extensions ExtensionList `tls:"head=2"`
}
func (cr CertificateRequestBody) Type() HandshakeType {
return HandshakeTypeCertificateRequest
}
func (cr CertificateRequestBody) Marshal() ([]byte, error) {
return syntax.Marshal(cr)
}
func (cr *CertificateRequestBody) Unmarshal(data []byte) (int, error) {
return syntax.Unmarshal(data, cr)
}
// struct {
// uint32 ticket_lifetime;
// uint32 ticket_age_add;
// opaque ticket_nonce<1..255>;
// opaque ticket<1..2^16-1>;
// Extension extensions<0..2^16-2>;
// } NewSessionTicket;
type NewSessionTicketBody struct {
TicketLifetime uint32
TicketAgeAdd uint32
TicketNonce []byte `tls:"head=1,min=1"`
Ticket []byte `tls:"head=2,min=1"`
Extensions ExtensionList `tls:"head=2"`
}
const ticketNonceLen = 16
func NewSessionTicket(ticketLen int, ticketLifetime uint32) (*NewSessionTicketBody, error) {
buf := make([]byte, 4+ticketNonceLen+ticketLen)
_, err := prng.Read(buf)
if err != nil {
return nil, err
}
tkt := &NewSessionTicketBody{
TicketLifetime: ticketLifetime,
TicketAgeAdd: binary.BigEndian.Uint32(buf[:4]),
TicketNonce: buf[4 : 4+ticketNonceLen],
Ticket: buf[4+ticketNonceLen:],
}
return tkt, err
}
func (tkt NewSessionTicketBody) Type() HandshakeType {
return HandshakeTypeNewSessionTicket
}
func (tkt NewSessionTicketBody) Marshal() ([]byte, error) {
return syntax.Marshal(tkt)
}
func (tkt *NewSessionTicketBody) Unmarshal(data []byte) (int, error) {
return syntax.Unmarshal(data, tkt)
}
// enum {
// update_not_requested(0), update_requested(1), (255)
// } KeyUpdateRequest;
//
// struct {
// KeyUpdateRequest request_update;
// } KeyUpdate;
type KeyUpdateBody struct {
KeyUpdateRequest KeyUpdateRequest
}
func (ku KeyUpdateBody) Type() HandshakeType {
return HandshakeTypeKeyUpdate
}
func (ku KeyUpdateBody) Marshal() ([]byte, error) {
return syntax.Marshal(ku)
}
func (ku *KeyUpdateBody) Unmarshal(data []byte) (int, error) {
return syntax.Unmarshal(data, ku)
}
// struct {} EndOfEarlyData;
type EndOfEarlyDataBody struct{}
func (eoed EndOfEarlyDataBody) Type() HandshakeType {
return HandshakeTypeEndOfEarlyData
}
func (eoed EndOfEarlyDataBody) Marshal() ([]byte, error) {
return []byte{}, nil
}
func (eoed *EndOfEarlyDataBody) Unmarshal(data []byte) (int, error) {
return 0, nil
}

View File

@ -1,55 +0,0 @@
package mint
import (
"fmt"
"log"
"os"
"strings"
)
// We use this environment variable to control logging. It should be a
// comma-separated list of log tags (see below) or "*" to enable all logging.
const logConfigVar = "MINT_LOG"
// Pre-defined log types
const (
logTypeCrypto = "crypto"
logTypeHandshake = "handshake"
logTypeNegotiation = "negotiation"
logTypeIO = "io"
logTypeFrameReader = "frame"
logTypeVerbose = "verbose"
)
var (
logFunction = log.Printf
logAll = false
logSettings = map[string]bool{}
)
func init() {
parseLogEnv(os.Environ())
}
func parseLogEnv(env []string) {
for _, stmt := range env {
if strings.HasPrefix(stmt, logConfigVar+"=") {
val := stmt[len(logConfigVar)+1:]
if val == "*" {
logAll = true
} else {
for _, t := range strings.Split(val, ",") {
logSettings[t] = true
}
}
}
}
}
func logf(tag string, format string, args ...interface{}) {
if logAll || logSettings[tag] {
fullFormat := fmt.Sprintf("[%s] %s", tag, format)
logFunction(fullFormat, args...)
}
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@ -1,217 +0,0 @@
package mint
import (
"bytes"
"encoding/hex"
"fmt"
"time"
)
func VersionNegotiation(offered, supported []uint16) (bool, uint16) {
for _, offeredVersion := range offered {
for _, supportedVersion := range supported {
logf(logTypeHandshake, "[server] version offered by client [%04x] <> [%04x]", offeredVersion, supportedVersion)
if offeredVersion == supportedVersion {
// XXX: Should probably be highest supported version, but for now, we
// only support one version, so it doesn't really matter.
return true, offeredVersion
}
}
}
return false, 0
}
func DHNegotiation(keyShares []KeyShareEntry, groups []NamedGroup) (bool, NamedGroup, []byte, []byte) {
for _, share := range keyShares {
for _, group := range groups {
if group != share.Group {
continue
}
pub, priv, err := newKeyShare(share.Group)
if err != nil {
// If we encounter an error, just keep looking
continue
}
dhSecret, err := keyAgreement(share.Group, share.KeyExchange, priv)
if err != nil {
// If we encounter an error, just keep looking
continue
}
return true, group, pub, dhSecret
}
}
return false, 0, nil, nil
}
const (
ticketAgeTolerance uint32 = 5 * 1000 // five seconds in milliseconds
)
func PSKNegotiation(identities []PSKIdentity, binders []PSKBinderEntry, context []byte, psks PreSharedKeyCache) (bool, int, *PreSharedKey, CipherSuiteParams, error) {
logf(logTypeNegotiation, "Negotiating PSK offered=[%d] supported=[%d]", len(identities), psks.Size())
for i, id := range identities {
identityHex := hex.EncodeToString(id.Identity)
psk, ok := psks.Get(identityHex)
if !ok {
logf(logTypeNegotiation, "No PSK for identity %x", identityHex)
continue
}
// For resumption, make sure the ticket age is correct
if psk.IsResumption {
extTicketAge := id.ObfuscatedTicketAge - psk.TicketAgeAdd
knownTicketAge := uint32(time.Since(psk.ReceivedAt) / time.Millisecond)
ticketAgeDelta := knownTicketAge - extTicketAge
if knownTicketAge < extTicketAge {
ticketAgeDelta = extTicketAge - knownTicketAge
}
if ticketAgeDelta > ticketAgeTolerance {
logf(logTypeNegotiation, "WARNING potential replay [%x]", psk.Identity)
logf(logTypeNegotiation, "Ticket age exceeds tolerance |%d - %d| = [%d] > [%d]",
extTicketAge, knownTicketAge, ticketAgeDelta, ticketAgeTolerance)
return false, 0, nil, CipherSuiteParams{}, fmt.Errorf("WARNING Potential replay for identity %x", psk.Identity)
}
}
params, ok := cipherSuiteMap[psk.CipherSuite]
if !ok {
err := fmt.Errorf("tls.cryptoinit: Unsupported ciphersuite from PSK [%04x]", psk.CipherSuite)
return false, 0, nil, CipherSuiteParams{}, err
}
// Compute binder
binderLabel := labelExternalBinder
if psk.IsResumption {
binderLabel = labelResumptionBinder
}
h0 := params.Hash.New().Sum(nil)
zero := bytes.Repeat([]byte{0}, params.Hash.Size())
earlySecret := HkdfExtract(params.Hash, zero, psk.Key)
binderKey := deriveSecret(params, earlySecret, binderLabel, h0)
// context = ClientHello[truncated]
// context = ClientHello1 + HelloRetryRequest + ClientHello2[truncated]
ctxHash := params.Hash.New()
ctxHash.Write(context)
binder := computeFinishedData(params, binderKey, ctxHash.Sum(nil))
if !bytes.Equal(binder, binders[i].Binder) {
logf(logTypeNegotiation, "Binder check failed for identity %x; [%x] != [%x]", psk.Identity, binder, binders[i].Binder)
return false, 0, nil, CipherSuiteParams{}, fmt.Errorf("Binder check failed identity %x", psk.Identity)
}
logf(logTypeNegotiation, "Using PSK with identity %x", psk.Identity)
return true, i, &psk, params, nil
}
logf(logTypeNegotiation, "Failed to find a usable PSK")
return false, 0, nil, CipherSuiteParams{}, nil
}
func PSKModeNegotiation(canDoDH, canDoPSK bool, modes []PSKKeyExchangeMode) (bool, bool) {
logf(logTypeNegotiation, "Negotiating PSK modes [%v] [%v] [%+v]", canDoDH, canDoPSK, modes)
dhAllowed := false
dhRequired := true
for _, mode := range modes {
dhAllowed = dhAllowed || (mode == PSKModeDHEKE)
dhRequired = dhRequired && (mode == PSKModeDHEKE)
}
// Use PSK if we can meet DH requirement and modes were provided
usingPSK := canDoPSK && (!dhRequired || canDoDH) && (len(modes) > 0)
// Use DH if allowed
usingDH := canDoDH && (dhAllowed || !usingPSK)
logf(logTypeNegotiation, "Results of PSK mode negotiation: usingDH=[%v] usingPSK=[%v]", usingDH, usingPSK)
return usingDH, usingPSK
}
func CertificateSelection(serverName *string, signatureSchemes []SignatureScheme, certs []*Certificate) (*Certificate, SignatureScheme, error) {
// Select for server name if provided
candidates := certs
if serverName != nil {
candidatesByName := []*Certificate{}
for _, cert := range certs {
for _, name := range cert.Chain[0].DNSNames {
if len(*serverName) > 0 && name == *serverName {
candidatesByName = append(candidatesByName, cert)
}
}
}
if len(candidatesByName) == 0 {
return nil, 0, fmt.Errorf("No certificates available for server name")
}
candidates = candidatesByName
}
// Select for signature scheme
for _, cert := range candidates {
for _, scheme := range signatureSchemes {
if !schemeValidForKey(scheme, cert.PrivateKey) {
continue
}
return cert, scheme, nil
}
}
return nil, 0, fmt.Errorf("No certificates compatible with signature schemes")
}
func EarlyDataNegotiation(usingPSK, gotEarlyData, allowEarlyData bool) bool {
usingEarlyData := gotEarlyData && usingPSK && allowEarlyData
logf(logTypeNegotiation, "Early data negotiation (%v, %v, %v) => %v", usingPSK, gotEarlyData, allowEarlyData, usingEarlyData)
return usingEarlyData
}
func CipherSuiteNegotiation(psk *PreSharedKey, offered, supported []CipherSuite) (CipherSuite, error) {
for _, s1 := range offered {
if psk != nil {
if s1 == psk.CipherSuite {
return s1, nil
}
continue
}
for _, s2 := range supported {
if s1 == s2 {
return s1, nil
}
}
}
return 0, fmt.Errorf("No overlap between offered and supproted ciphersuites (psk? [%v])", psk != nil)
}
func ALPNNegotiation(psk *PreSharedKey, offered, supported []string) (string, error) {
for _, p1 := range offered {
if psk != nil {
if p1 != psk.NextProto {
continue
}
}
for _, p2 := range supported {
if p1 == p2 {
return p1, nil
}
}
}
// If the client offers ALPN on resumption, it must match the earlier one
var err error
if psk != nil && psk.IsResumption && (len(offered) > 0) {
err = fmt.Errorf("ALPN for PSK not provided")
}
return "", err
}

View File

@ -1,296 +0,0 @@
package mint
import (
"bytes"
"crypto/cipher"
"fmt"
"io"
"sync"
)
const (
sequenceNumberLen = 8 // sequence number length
recordHeaderLen = 5 // record header length
maxFragmentLen = 1 << 14 // max number of bytes in a record
)
type DecryptError string
func (err DecryptError) Error() string {
return string(err)
}
// struct {
// ContentType type;
// ProtocolVersion record_version = { 3, 1 }; /* TLS v1.x */
// uint16 length;
// opaque fragment[TLSPlaintext.length];
// } TLSPlaintext;
type TLSPlaintext struct {
// Omitted: record_version (static)
// Omitted: length (computed from fragment)
contentType RecordType
fragment []byte
}
type RecordLayer struct {
sync.Mutex
conn io.ReadWriter // The underlying connection
frame *frameReader // The buffered frame reader
nextData []byte // The next record to send
cachedRecord *TLSPlaintext // Last record read, cached to enable "peek"
cachedError error // Error on the last record read
ivLength int // Length of the seq and nonce fields
seq []byte // Zero-padded sequence number
nonce []byte // Buffer for per-record nonces
cipher cipher.AEAD // AEAD cipher
}
type recordLayerFrameDetails struct{}
func (d recordLayerFrameDetails) headerLen() int {
return recordHeaderLen
}
func (d recordLayerFrameDetails) defaultReadLen() int {
return recordHeaderLen + maxFragmentLen
}
func (d recordLayerFrameDetails) frameLen(hdr []byte) (int, error) {
return (int(hdr[3]) << 8) | int(hdr[4]), nil
}
func NewRecordLayer(conn io.ReadWriter) *RecordLayer {
r := RecordLayer{}
r.conn = conn
r.frame = newFrameReader(recordLayerFrameDetails{})
r.ivLength = 0
return &r
}
func (r *RecordLayer) Rekey(cipher aeadFactory, key []byte, iv []byte) error {
var err error
r.cipher, err = cipher(key)
if err != nil {
return err
}
r.ivLength = len(iv)
r.seq = bytes.Repeat([]byte{0}, r.ivLength)
r.nonce = make([]byte, r.ivLength)
copy(r.nonce, iv)
return nil
}
func (r *RecordLayer) incrementSequenceNumber() {
if r.ivLength == 0 {
return
}
for i := r.ivLength - 1; i > r.ivLength-sequenceNumberLen; i-- {
r.seq[i]++
r.nonce[i] ^= (r.seq[i] - 1) ^ r.seq[i]
if r.seq[i] != 0 {
return
}
}
// Not allowed to let sequence number wrap.
// Instead, must renegotiate before it does.
// Not likely enough to bother.
panic("TLS: sequence number wraparound")
}
func (r *RecordLayer) encrypt(pt *TLSPlaintext, padLen int) *TLSPlaintext {
// Expand the fragment to hold contentType, padding, and overhead
originalLen := len(pt.fragment)
plaintextLen := originalLen + 1 + padLen
ciphertextLen := plaintextLen + r.cipher.Overhead()
// Assemble the revised plaintext
out := &TLSPlaintext{
contentType: RecordTypeApplicationData,
fragment: make([]byte, ciphertextLen),
}
copy(out.fragment, pt.fragment)
out.fragment[originalLen] = byte(pt.contentType)
for i := 1; i <= padLen; i++ {
out.fragment[originalLen+i] = 0
}
// Encrypt the fragment
payload := out.fragment[:plaintextLen]
r.cipher.Seal(payload[:0], r.nonce, payload, nil)
return out
}
func (r *RecordLayer) decrypt(pt *TLSPlaintext) (*TLSPlaintext, int, error) {
if len(pt.fragment) < r.cipher.Overhead() {
msg := fmt.Sprintf("tls.record.decrypt: Record too short [%d] < [%d]", len(pt.fragment), r.cipher.Overhead())
return nil, 0, DecryptError(msg)
}
decryptLen := len(pt.fragment) - r.cipher.Overhead()
out := &TLSPlaintext{
contentType: pt.contentType,
fragment: make([]byte, decryptLen),
}
// Decrypt
_, err := r.cipher.Open(out.fragment[:0], r.nonce, pt.fragment, nil)
if err != nil {
return nil, 0, DecryptError("tls.record.decrypt: AEAD decrypt failed")
}
// Find the padding boundary
padLen := 0
for ; padLen < decryptLen+1 && out.fragment[decryptLen-padLen-1] == 0; padLen++ {
}
// Transfer the content type
newLen := decryptLen - padLen - 1
out.contentType = RecordType(out.fragment[newLen])
// Truncate the message to remove contentType, padding, overhead
out.fragment = out.fragment[:newLen]
return out, padLen, nil
}
func (r *RecordLayer) PeekRecordType(block bool) (RecordType, error) {
var pt *TLSPlaintext
var err error
for {
pt, err = r.nextRecord()
if err == nil {
break
}
if !block || err != WouldBlock {
return 0, err
}
}
return pt.contentType, nil
}
func (r *RecordLayer) ReadRecord() (*TLSPlaintext, error) {
pt, err := r.nextRecord()
// Consume the cached record if there was one
r.cachedRecord = nil
r.cachedError = nil
return pt, err
}
func (r *RecordLayer) nextRecord() (*TLSPlaintext, error) {
if r.cachedRecord != nil {
logf(logTypeIO, "Returning cached record")
return r.cachedRecord, r.cachedError
}
// Loop until one of three things happens:
//
// 1. We get a frame
// 2. We try to read off the socket and get nothing, in which case
// return WouldBlock
// 3. We get an error.
err := WouldBlock
var header, body []byte
for err != nil {
if r.frame.needed() > 0 {
buf := make([]byte, recordHeaderLen+maxFragmentLen)
n, err := r.conn.Read(buf)
if err != nil {
logf(logTypeIO, "Error reading, %v", err)
return nil, err
}
if n == 0 {
return nil, WouldBlock
}
logf(logTypeIO, "Read %v bytes", n)
buf = buf[:n]
r.frame.addChunk(buf)
}
header, body, err = r.frame.process()
// Loop around on WouldBlock to see if some
// data is now available.
if err != nil && err != WouldBlock {
return nil, err
}
}
pt := &TLSPlaintext{}
// Validate content type
switch RecordType(header[0]) {
default:
return nil, fmt.Errorf("tls.record: Unknown content type %02x", header[0])
case RecordTypeAlert, RecordTypeHandshake, RecordTypeApplicationData:
pt.contentType = RecordType(header[0])
}
// Validate version
if !allowWrongVersionNumber && (header[1] != 0x03 || header[2] != 0x01) {
return nil, fmt.Errorf("tls.record: Invalid version %02x%02x", header[1], header[2])
}
// Validate size < max
size := (int(header[3]) << 8) + int(header[4])
if size > maxFragmentLen+256 {
return nil, fmt.Errorf("tls.record: Ciphertext size too big")
}
pt.fragment = make([]byte, size)
copy(pt.fragment, body)
// Attempt to decrypt fragment
if r.cipher != nil {
pt, _, err = r.decrypt(pt)
if err != nil {
return nil, err
}
}
// Check that plaintext length is not too long
if len(pt.fragment) > maxFragmentLen {
return nil, fmt.Errorf("tls.record: Plaintext size too big")
}
logf(logTypeIO, "RecordLayer.ReadRecord [%d] [%x]", pt.contentType, pt.fragment)
r.cachedRecord = pt
r.incrementSequenceNumber()
return pt, nil
}
func (r *RecordLayer) WriteRecord(pt *TLSPlaintext) error {
return r.WriteRecordWithPadding(pt, 0)
}
func (r *RecordLayer) WriteRecordWithPadding(pt *TLSPlaintext, padLen int) error {
if r.cipher != nil {
pt = r.encrypt(pt, padLen)
} else if padLen > 0 {
return fmt.Errorf("tls.record: Padding can only be done on encrypted records")
}
if len(pt.fragment) > maxFragmentLen {
return fmt.Errorf("tls.record: Record size too big")
}
length := len(pt.fragment)
header := []byte{byte(pt.contentType), 0x03, 0x01, byte(length >> 8), byte(length)}
record := append(header, pt.fragment...)
logf(logTypeIO, "RecordLayer.WriteRecord [%d] [%x]", pt.contentType, pt.fragment)
r.incrementSequenceNumber()
_, err := r.conn.Write(record)
return err
}

View File

@ -1,898 +0,0 @@
package mint
import (
"bytes"
"hash"
"reflect"
)
// Server State Machine
//
// START <-----+
// Recv ClientHello | | Send HelloRetryRequest
// v |
// RECVD_CH ----+
// | Select parameters
// | Send ServerHello
// v
// NEGOTIATED
// | Send EncryptedExtensions
// | [Send CertificateRequest]
// Can send | [Send Certificate + CertificateVerify]
// app data --> | Send Finished
// after +--------+--------+
// here No 0-RTT | | 0-RTT
// | v
// | WAIT_EOED <---+
// | Recv | | | Recv
// | EndOfEarlyData | | | early data
// | | +-----+
// +> WAIT_FLIGHT2 <-+
// |
// +--------+--------+
// No auth | | Client auth
// | |
// | v
// | WAIT_CERT
// | Recv | | Recv Certificate
// | empty | v
// | Certificate | WAIT_CV
// | | | Recv
// | v | CertificateVerify
// +-> WAIT_FINISHED <---+
// | Recv Finished
// v
// CONNECTED
//
// NB: Not using state RECVD_CH
//
// State Instructions
// START {}
// NEGOTIATED Send(SH); [RekeyIn;] RekeyOut; Send(EE); [Send(CertReq);] [Send(Cert); Send(CV)]
// WAIT_EOED RekeyIn;
// WAIT_FLIGHT2 {}
// WAIT_CERT_CR {}
// WAIT_CERT {}
// WAIT_CV {}
// WAIT_FINISHED RekeyIn; RekeyOut;
// CONNECTED StoreTicket || (RekeyIn; [RekeyOut])
type ServerStateStart struct {
Caps Capabilities
conn *Conn
cookieSent bool
firstClientHello *HandshakeMessage
helloRetryRequest *HandshakeMessage
}
func (state ServerStateStart) Next(hm *HandshakeMessage) (HandshakeState, []HandshakeAction, Alert) {
if hm == nil || hm.msgType != HandshakeTypeClientHello {
logf(logTypeHandshake, "[ServerStateStart] unexpected message")
return nil, nil, AlertUnexpectedMessage
}
ch := &ClientHelloBody{}
_, err := ch.Unmarshal(hm.body)
if err != nil {
logf(logTypeHandshake, "[ServerStateStart] Error decoding message: %v", err)
return nil, nil, AlertDecodeError
}
clientHello := hm
connParams := ConnectionParameters{}
supportedVersions := new(SupportedVersionsExtension)
serverName := new(ServerNameExtension)
supportedGroups := new(SupportedGroupsExtension)
signatureAlgorithms := new(SignatureAlgorithmsExtension)
clientKeyShares := &KeyShareExtension{HandshakeType: HandshakeTypeClientHello}
clientPSK := &PreSharedKeyExtension{HandshakeType: HandshakeTypeClientHello}
clientEarlyData := &EarlyDataExtension{}
clientALPN := new(ALPNExtension)
clientPSKModes := new(PSKKeyExchangeModesExtension)
clientCookie := new(CookieExtension)
// Handle external extensions.
if state.Caps.ExtensionHandler != nil {
err := state.Caps.ExtensionHandler.Receive(HandshakeTypeClientHello, &ch.Extensions)
if err != nil {
logf(logTypeHandshake, "[ServerStateStart] Error running external extension handler [%v]", err)
return nil, nil, AlertInternalError
}
}
gotSupportedVersions := ch.Extensions.Find(supportedVersions)
gotServerName := ch.Extensions.Find(serverName)
gotSupportedGroups := ch.Extensions.Find(supportedGroups)
gotSignatureAlgorithms := ch.Extensions.Find(signatureAlgorithms)
gotEarlyData := ch.Extensions.Find(clientEarlyData)
ch.Extensions.Find(clientKeyShares)
ch.Extensions.Find(clientPSK)
ch.Extensions.Find(clientALPN)
ch.Extensions.Find(clientPSKModes)
ch.Extensions.Find(clientCookie)
if gotServerName {
connParams.ServerName = string(*serverName)
}
// If the client didn't send supportedVersions or doesn't support 1.3,
// then we're done here.
if !gotSupportedVersions {
logf(logTypeHandshake, "[ServerStateStart] Client did not send supported_versions")
return nil, nil, AlertProtocolVersion
}
versionOK, _ := VersionNegotiation(supportedVersions.Versions, []uint16{supportedVersion})
if !versionOK {
logf(logTypeHandshake, "[ServerStateStart] Client does not support the same version")
return nil, nil, AlertProtocolVersion
}
if state.Caps.RequireCookie && state.cookieSent && !state.Caps.CookieHandler.Validate(state.conn, clientCookie.Cookie) {
logf(logTypeHandshake, "[ServerStateStart] Cookie mismatch")
return nil, nil, AlertAccessDenied
}
// Figure out if we can do DH
canDoDH, dhGroup, dhPublic, dhSecret := DHNegotiation(clientKeyShares.Shares, state.Caps.Groups)
// Figure out if we can do PSK
canDoPSK := false
var selectedPSK int
var psk *PreSharedKey
var params CipherSuiteParams
if len(clientPSK.Identities) > 0 {
contextBase := []byte{}
if state.helloRetryRequest != nil {
chBytes := state.firstClientHello.Marshal()
hrrBytes := state.helloRetryRequest.Marshal()
contextBase = append(chBytes, hrrBytes...)
}
chTrunc, err := ch.Truncated()
if err != nil {
logf(logTypeHandshake, "[ServerStateStart] Error computing truncated ClientHello [%v]", err)
return nil, nil, AlertDecodeError
}
context := append(contextBase, chTrunc...)
canDoPSK, selectedPSK, psk, params, err = PSKNegotiation(clientPSK.Identities, clientPSK.Binders, context, state.Caps.PSKs)
if err != nil {
logf(logTypeHandshake, "[ServerStateStart] Error in PSK negotiation [%v]", err)
return nil, nil, AlertInternalError
}
}
// Figure out if we actually should do DH / PSK
connParams.UsingDH, connParams.UsingPSK = PSKModeNegotiation(canDoDH, canDoPSK, clientPSKModes.KEModes)
// Select a ciphersuite
connParams.CipherSuite, err = CipherSuiteNegotiation(psk, ch.CipherSuites, state.Caps.CipherSuites)
if err != nil {
logf(logTypeHandshake, "[ServerStateStart] No common ciphersuite found [%v]", err)
return nil, nil, AlertHandshakeFailure
}
// Send a cookie if required
// NB: Need to do this here because it's after ciphersuite selection, which
// has to be after PSK selection.
// XXX: Doing this statefully for now, could be stateless
var cookieData []byte
if state.Caps.RequireCookie && !state.cookieSent {
var err error
cookieData, err = state.Caps.CookieHandler.Generate(state.conn)
if err != nil {
logf(logTypeHandshake, "[ServerStateStart] Error generating cookie [%v]", err)
return nil, nil, AlertInternalError
}
}
if cookieData != nil {
// Ignoring errors because everything here is newly constructed, so there
// shouldn't be marshal errors
hrr := &HelloRetryRequestBody{
Version: supportedVersion,
CipherSuite: connParams.CipherSuite,
}
hrr.Extensions.Add(&CookieExtension{Cookie: cookieData})
// Run the external extension handler.
if state.Caps.ExtensionHandler != nil {
err := state.Caps.ExtensionHandler.Send(HandshakeTypeHelloRetryRequest, &hrr.Extensions)
if err != nil {
logf(logTypeHandshake, "[ServerStateStart] Error running external extension sender [%v]", err)
return nil, nil, AlertInternalError
}
}
helloRetryRequest, err := HandshakeMessageFromBody(hrr)
if err != nil {
logf(logTypeHandshake, "[ServerStateStart] Error marshaling HRR [%v]", err)
return nil, nil, AlertInternalError
}
params := cipherSuiteMap[connParams.CipherSuite]
h := params.Hash.New()
h.Write(clientHello.Marshal())
firstClientHello := &HandshakeMessage{
msgType: HandshakeTypeMessageHash,
body: h.Sum(nil),
}
nextState := ServerStateStart{
Caps: state.Caps,
conn: state.conn,
cookieSent: true,
firstClientHello: firstClientHello,
helloRetryRequest: helloRetryRequest,
}
toSend := []HandshakeAction{SendHandshakeMessage{helloRetryRequest}}
logf(logTypeHandshake, "[ServerStateStart] -> [ServerStateStart]")
return nextState, toSend, AlertNoAlert
}
// If we've got no entropy to make keys from, fail
if !connParams.UsingDH && !connParams.UsingPSK {
logf(logTypeHandshake, "[ServerStateStart] Neither DH nor PSK negotiated")
return nil, nil, AlertHandshakeFailure
}
var pskSecret []byte
var cert *Certificate
var certScheme SignatureScheme
if connParams.UsingPSK {
pskSecret = psk.Key
} else {
psk = nil
// If we're not using a PSK mode, then we need to have certain extensions
if !gotServerName || !gotSupportedGroups || !gotSignatureAlgorithms {
logf(logTypeHandshake, "[ServerStateStart] Insufficient extensions (%v %v %v)",
gotServerName, gotSupportedGroups, gotSignatureAlgorithms)
return nil, nil, AlertMissingExtension
}
// Select a certificate
name := string(*serverName)
var err error
cert, certScheme, err = CertificateSelection(&name, signatureAlgorithms.Algorithms, state.Caps.Certificates)
if err != nil {
logf(logTypeHandshake, "[ServerStateStart] No appropriate certificate found [%v]", err)
return nil, nil, AlertAccessDenied
}
}
if !connParams.UsingDH {
dhSecret = nil
}
// Figure out if we're going to do early data
var clientEarlyTrafficSecret []byte
connParams.ClientSendingEarlyData = gotEarlyData
connParams.UsingEarlyData = EarlyDataNegotiation(connParams.UsingPSK, gotEarlyData, state.Caps.AllowEarlyData)
if connParams.UsingEarlyData {
h := params.Hash.New()
h.Write(clientHello.Marshal())
chHash := h.Sum(nil)
zero := bytes.Repeat([]byte{0}, params.Hash.Size())
earlySecret := HkdfExtract(params.Hash, zero, pskSecret)
clientEarlyTrafficSecret = deriveSecret(params, earlySecret, labelEarlyTrafficSecret, chHash)
}
// Select a next protocol
connParams.NextProto, err = ALPNNegotiation(psk, clientALPN.Protocols, state.Caps.NextProtos)
if err != nil {
logf(logTypeHandshake, "[ServerStateStart] No common application-layer protocol found [%v]", err)
return nil, nil, AlertNoApplicationProtocol
}
logf(logTypeHandshake, "[ServerStateStart] -> [ServerStateNegotiated]")
return ServerStateNegotiated{
Caps: state.Caps,
Params: connParams,
dhGroup: dhGroup,
dhPublic: dhPublic,
dhSecret: dhSecret,
pskSecret: pskSecret,
selectedPSK: selectedPSK,
cert: cert,
certScheme: certScheme,
clientEarlyTrafficSecret: clientEarlyTrafficSecret,
firstClientHello: state.firstClientHello,
helloRetryRequest: state.helloRetryRequest,
clientHello: clientHello,
}.Next(nil)
}
type ServerStateNegotiated struct {
Caps Capabilities
Params ConnectionParameters
dhGroup NamedGroup
dhPublic []byte
dhSecret []byte
pskSecret []byte
clientEarlyTrafficSecret []byte
selectedPSK int
cert *Certificate
certScheme SignatureScheme
firstClientHello *HandshakeMessage
helloRetryRequest *HandshakeMessage
clientHello *HandshakeMessage
}
func (state ServerStateNegotiated) Next(hm *HandshakeMessage) (HandshakeState, []HandshakeAction, Alert) {
if hm != nil {
logf(logTypeHandshake, "[ServerStateNegotiated] Unexpected message")
return nil, nil, AlertUnexpectedMessage
}
// Create the ServerHello
sh := &ServerHelloBody{
Version: supportedVersion,
CipherSuite: state.Params.CipherSuite,
}
_, err := prng.Read(sh.Random[:])
if err != nil {
logf(logTypeHandshake, "[ServerStateNegotiated] Error creating server random [%v]", err)
return nil, nil, AlertInternalError
}
if state.Params.UsingDH {
logf(logTypeHandshake, "[ServerStateNegotiated] sending DH extension")
err = sh.Extensions.Add(&KeyShareExtension{
HandshakeType: HandshakeTypeServerHello,
Shares: []KeyShareEntry{{Group: state.dhGroup, KeyExchange: state.dhPublic}},
})
if err != nil {
logf(logTypeHandshake, "[ServerStateNegotiated] Error adding key_shares extension [%v]", err)
return nil, nil, AlertInternalError
}
}
if state.Params.UsingPSK {
logf(logTypeHandshake, "[ServerStateNegotiated] sending PSK extension")
err = sh.Extensions.Add(&PreSharedKeyExtension{
HandshakeType: HandshakeTypeServerHello,
SelectedIdentity: uint16(state.selectedPSK),
})
if err != nil {
logf(logTypeHandshake, "[ServerStateNegotiated] Error adding PSK extension [%v]", err)
return nil, nil, AlertInternalError
}
}
// Run the external extension handler.
if state.Caps.ExtensionHandler != nil {
err := state.Caps.ExtensionHandler.Send(HandshakeTypeServerHello, &sh.Extensions)
if err != nil {
logf(logTypeHandshake, "[ServerStateNegotiated] Error running external extension sender [%v]", err)
return nil, nil, AlertInternalError
}
}
serverHello, err := HandshakeMessageFromBody(sh)
if err != nil {
logf(logTypeHandshake, "[ServerStateNegotiated] Error marshaling ServerHello [%v]", err)
return nil, nil, AlertInternalError
}
// Look up crypto params
params, ok := cipherSuiteMap[sh.CipherSuite]
if !ok {
logf(logTypeCrypto, "Unsupported ciphersuite [%04x]", sh.CipherSuite)
return nil, nil, AlertHandshakeFailure
}
// Start up the handshake hash
handshakeHash := params.Hash.New()
handshakeHash.Write(state.firstClientHello.Marshal())
handshakeHash.Write(state.helloRetryRequest.Marshal())
handshakeHash.Write(state.clientHello.Marshal())
handshakeHash.Write(serverHello.Marshal())
// Compute handshake secrets
zero := bytes.Repeat([]byte{0}, params.Hash.Size())
var earlySecret []byte
if state.Params.UsingPSK {
earlySecret = HkdfExtract(params.Hash, zero, state.pskSecret)
} else {
earlySecret = HkdfExtract(params.Hash, zero, zero)
}
if state.dhSecret == nil {
state.dhSecret = zero
}
h0 := params.Hash.New().Sum(nil)
h2 := handshakeHash.Sum(nil)
preHandshakeSecret := deriveSecret(params, earlySecret, labelDerived, h0)
handshakeSecret := HkdfExtract(params.Hash, preHandshakeSecret, state.dhSecret)
clientHandshakeTrafficSecret := deriveSecret(params, handshakeSecret, labelClientHandshakeTrafficSecret, h2)
serverHandshakeTrafficSecret := deriveSecret(params, handshakeSecret, labelServerHandshakeTrafficSecret, h2)
preMasterSecret := deriveSecret(params, handshakeSecret, labelDerived, h0)
masterSecret := HkdfExtract(params.Hash, preMasterSecret, zero)
logf(logTypeCrypto, "early secret (init!): [%d] %x", len(earlySecret), earlySecret)
logf(logTypeCrypto, "handshake secret: [%d] %x", len(handshakeSecret), handshakeSecret)
logf(logTypeCrypto, "client handshake traffic secret: [%d] %x", len(clientHandshakeTrafficSecret), clientHandshakeTrafficSecret)
logf(logTypeCrypto, "server handshake traffic secret: [%d] %x", len(serverHandshakeTrafficSecret), serverHandshakeTrafficSecret)
logf(logTypeCrypto, "master secret: [%d] %x", len(masterSecret), masterSecret)
clientHandshakeKeys := makeTrafficKeys(params, clientHandshakeTrafficSecret)
serverHandshakeKeys := makeTrafficKeys(params, serverHandshakeTrafficSecret)
// Send an EncryptedExtensions message (even if it's empty)
eeList := ExtensionList{}
if state.Params.NextProto != "" {
logf(logTypeHandshake, "[server] sending ALPN extension")
err = eeList.Add(&ALPNExtension{Protocols: []string{state.Params.NextProto}})
if err != nil {
logf(logTypeHandshake, "[ServerStateNegotiated] Error adding ALPN to EncryptedExtensions [%v]", err)
return nil, nil, AlertInternalError
}
}
if state.Params.UsingEarlyData {
logf(logTypeHandshake, "[server] sending EDI extension")
err = eeList.Add(&EarlyDataExtension{})
if err != nil {
logf(logTypeHandshake, "[ServerStateNegotiated] Error adding EDI to EncryptedExtensions [%v]", err)
return nil, nil, AlertInternalError
}
}
ee := &EncryptedExtensionsBody{eeList}
// Run the external extension handler.
if state.Caps.ExtensionHandler != nil {
err := state.Caps.ExtensionHandler.Send(HandshakeTypeEncryptedExtensions, &ee.Extensions)
if err != nil {
logf(logTypeHandshake, "[ServerStateNegotiated] Error running external extension sender [%v]", err)
return nil, nil, AlertInternalError
}
}
eem, err := HandshakeMessageFromBody(ee)
if err != nil {
logf(logTypeHandshake, "[ServerStateNegotiated] Error marshaling EncryptedExtensions [%v]", err)
return nil, nil, AlertInternalError
}
handshakeHash.Write(eem.Marshal())
toSend := []HandshakeAction{
SendHandshakeMessage{serverHello},
RekeyOut{Label: "handshake", KeySet: serverHandshakeKeys},
SendHandshakeMessage{eem},
}
// Authenticate with a certificate if required
if !state.Params.UsingPSK {
// Send a CertificateRequest message if we want client auth
if state.Caps.RequireClientAuth {
state.Params.UsingClientAuth = true
// XXX: We don't support sending any constraints besides a list of
// supported signature algorithms
cr := &CertificateRequestBody{}
schemes := &SignatureAlgorithmsExtension{Algorithms: state.Caps.SignatureSchemes}
err := cr.Extensions.Add(schemes)
if err != nil {
logf(logTypeHandshake, "[ServerStateNegotiated] Error adding supported schemes to CertificateRequest [%v]", err)
return nil, nil, AlertInternalError
}
crm, err := HandshakeMessageFromBody(cr)
if err != nil {
logf(logTypeHandshake, "[ServerStateNegotiated] Error marshaling CertificateRequest [%v]", err)
return nil, nil, AlertInternalError
}
//TODO state.state.serverCertificateRequest = cr
toSend = append(toSend, SendHandshakeMessage{crm})
handshakeHash.Write(crm.Marshal())
}
// Create and send Certificate, CertificateVerify
certificate := &CertificateBody{
CertificateList: make([]CertificateEntry, len(state.cert.Chain)),
}
for i, entry := range state.cert.Chain {
certificate.CertificateList[i] = CertificateEntry{CertData: entry}
}
certm, err := HandshakeMessageFromBody(certificate)
if err != nil {
logf(logTypeHandshake, "[ServerStateNegotiated] Error marshaling Certificate [%v]", err)
return nil, nil, AlertInternalError
}
toSend = append(toSend, SendHandshakeMessage{certm})
handshakeHash.Write(certm.Marshal())
certificateVerify := &CertificateVerifyBody{Algorithm: state.certScheme}
logf(logTypeHandshake, "Creating CertVerify: %04x %v", state.certScheme, params.Hash)
hcv := handshakeHash.Sum(nil)
logf(logTypeHandshake, "Handshake Hash to be verified: [%d] %x", len(hcv), hcv)
err = certificateVerify.Sign(state.cert.PrivateKey, hcv)
if err != nil {
logf(logTypeHandshake, "[ServerStateNegotiated] Error signing CertificateVerify [%v]", err)
return nil, nil, AlertInternalError
}
certvm, err := HandshakeMessageFromBody(certificateVerify)
if err != nil {
logf(logTypeHandshake, "[ServerStateNegotiated] Error marshaling CertificateVerify [%v]", err)
return nil, nil, AlertInternalError
}
toSend = append(toSend, SendHandshakeMessage{certvm})
handshakeHash.Write(certvm.Marshal())
}
// Compute secrets resulting from the server's first flight
h3 := handshakeHash.Sum(nil)
logf(logTypeCrypto, "handshake hash 3 [%d] %x", len(h3), h3)
logf(logTypeCrypto, "handshake hash for server Finished: [%d] %x", len(h3), h3)
serverFinishedData := computeFinishedData(params, serverHandshakeTrafficSecret, h3)
logf(logTypeCrypto, "server finished data: [%d] %x", len(serverFinishedData), serverFinishedData)
// Assemble the Finished message
fin := &FinishedBody{
VerifyDataLen: len(serverFinishedData),
VerifyData: serverFinishedData,
}
finm, _ := HandshakeMessageFromBody(fin)
toSend = append(toSend, SendHandshakeMessage{finm})
handshakeHash.Write(finm.Marshal())
// Compute traffic secrets
h4 := handshakeHash.Sum(nil)
logf(logTypeCrypto, "handshake hash 4 [%d] %x", len(h4), h4)
logf(logTypeCrypto, "handshake hash for server Finished: [%d] %x", len(h4), h4)
clientTrafficSecret := deriveSecret(params, masterSecret, labelClientApplicationTrafficSecret, h4)
serverTrafficSecret := deriveSecret(params, masterSecret, labelServerApplicationTrafficSecret, h4)
logf(logTypeCrypto, "client traffic secret: [%d] %x", len(clientTrafficSecret), clientTrafficSecret)
logf(logTypeCrypto, "server traffic secret: [%d] %x", len(serverTrafficSecret), serverTrafficSecret)
serverTrafficKeys := makeTrafficKeys(params, serverTrafficSecret)
toSend = append(toSend, RekeyOut{Label: "application", KeySet: serverTrafficKeys})
exporterSecret := deriveSecret(params, masterSecret, labelExporterSecret, h4)
logf(logTypeCrypto, "server exporter secret: [%d] %x", len(exporterSecret), exporterSecret)
if state.Params.UsingEarlyData {
clientEarlyTrafficKeys := makeTrafficKeys(params, state.clientEarlyTrafficSecret)
logf(logTypeHandshake, "[ServerStateNegotiated] -> [ServerStateWaitEOED]")
nextState := ServerStateWaitEOED{
AuthCertificate: state.Caps.AuthCertificate,
Params: state.Params,
cryptoParams: params,
handshakeHash: handshakeHash,
masterSecret: masterSecret,
clientHandshakeTrafficSecret: clientHandshakeTrafficSecret,
clientTrafficSecret: clientTrafficSecret,
serverTrafficSecret: serverTrafficSecret,
exporterSecret: exporterSecret,
}
toSend = append(toSend, []HandshakeAction{
RekeyIn{Label: "early", KeySet: clientEarlyTrafficKeys},
ReadEarlyData{},
}...)
return nextState, toSend, AlertNoAlert
}
logf(logTypeHandshake, "[ServerStateNegotiated] -> [ServerStateWaitFlight2]")
toSend = append(toSend, []HandshakeAction{
RekeyIn{Label: "handshake", KeySet: clientHandshakeKeys},
ReadPastEarlyData{},
}...)
waitFlight2 := ServerStateWaitFlight2{
AuthCertificate: state.Caps.AuthCertificate,
Params: state.Params,
cryptoParams: params,
handshakeHash: handshakeHash,
masterSecret: masterSecret,
clientHandshakeTrafficSecret: clientHandshakeTrafficSecret,
clientTrafficSecret: clientTrafficSecret,
serverTrafficSecret: serverTrafficSecret,
exporterSecret: exporterSecret,
}
nextState, moreToSend, alert := waitFlight2.Next(nil)
toSend = append(toSend, moreToSend...)
return nextState, toSend, alert
}
type ServerStateWaitEOED struct {
AuthCertificate func(chain []CertificateEntry) error
Params ConnectionParameters
cryptoParams CipherSuiteParams
masterSecret []byte
clientHandshakeTrafficSecret []byte
handshakeHash hash.Hash
clientTrafficSecret []byte
serverTrafficSecret []byte
exporterSecret []byte
}
func (state ServerStateWaitEOED) Next(hm *HandshakeMessage) (HandshakeState, []HandshakeAction, Alert) {
if hm == nil || hm.msgType != HandshakeTypeEndOfEarlyData {
logf(logTypeHandshake, "[ServerStateWaitEOED] Unexpected message")
return nil, nil, AlertUnexpectedMessage
}
if len(hm.body) > 0 {
logf(logTypeHandshake, "[ServerStateWaitEOED] Error decoding message [len > 0]")
return nil, nil, AlertDecodeError
}
state.handshakeHash.Write(hm.Marshal())
clientHandshakeKeys := makeTrafficKeys(state.cryptoParams, state.clientHandshakeTrafficSecret)
logf(logTypeHandshake, "[ServerStateWaitEOED] -> [ServerStateWaitFlight2]")
toSend := []HandshakeAction{
RekeyIn{Label: "handshake", KeySet: clientHandshakeKeys},
}
waitFlight2 := ServerStateWaitFlight2{
AuthCertificate: state.AuthCertificate,
Params: state.Params,
cryptoParams: state.cryptoParams,
handshakeHash: state.handshakeHash,
masterSecret: state.masterSecret,
clientHandshakeTrafficSecret: state.clientHandshakeTrafficSecret,
clientTrafficSecret: state.clientTrafficSecret,
serverTrafficSecret: state.serverTrafficSecret,
exporterSecret: state.exporterSecret,
}
nextState, moreToSend, alert := waitFlight2.Next(nil)
toSend = append(toSend, moreToSend...)
return nextState, toSend, alert
}
type ServerStateWaitFlight2 struct {
AuthCertificate func(chain []CertificateEntry) error
Params ConnectionParameters
cryptoParams CipherSuiteParams
masterSecret []byte
clientHandshakeTrafficSecret []byte
handshakeHash hash.Hash
clientTrafficSecret []byte
serverTrafficSecret []byte
exporterSecret []byte
}
func (state ServerStateWaitFlight2) Next(hm *HandshakeMessage) (HandshakeState, []HandshakeAction, Alert) {
if hm != nil {
logf(logTypeHandshake, "[ServerStateWaitFlight2] Unexpected message")
return nil, nil, AlertUnexpectedMessage
}
if state.Params.UsingClientAuth {
logf(logTypeHandshake, "[ServerStateWaitFlight2] -> [ServerStateWaitCert]")
nextState := ServerStateWaitCert{
AuthCertificate: state.AuthCertificate,
Params: state.Params,
cryptoParams: state.cryptoParams,
handshakeHash: state.handshakeHash,
masterSecret: state.masterSecret,
clientHandshakeTrafficSecret: state.clientHandshakeTrafficSecret,
clientTrafficSecret: state.clientTrafficSecret,
serverTrafficSecret: state.serverTrafficSecret,
exporterSecret: state.exporterSecret,
}
return nextState, nil, AlertNoAlert
}
logf(logTypeHandshake, "[ServerStateWaitFlight2] -> [ServerStateWaitFinished]")
nextState := ServerStateWaitFinished{
Params: state.Params,
cryptoParams: state.cryptoParams,
masterSecret: state.masterSecret,
clientHandshakeTrafficSecret: state.clientHandshakeTrafficSecret,
handshakeHash: state.handshakeHash,
clientTrafficSecret: state.clientTrafficSecret,
serverTrafficSecret: state.serverTrafficSecret,
exporterSecret: state.exporterSecret,
}
return nextState, nil, AlertNoAlert
}
type ServerStateWaitCert struct {
AuthCertificate func(chain []CertificateEntry) error
Params ConnectionParameters
cryptoParams CipherSuiteParams
masterSecret []byte
clientHandshakeTrafficSecret []byte
handshakeHash hash.Hash
clientTrafficSecret []byte
serverTrafficSecret []byte
exporterSecret []byte
}
func (state ServerStateWaitCert) Next(hm *HandshakeMessage) (HandshakeState, []HandshakeAction, Alert) {
if hm == nil || hm.msgType != HandshakeTypeCertificate {
logf(logTypeHandshake, "[ServerStateWaitCert] Unexpected message")
return nil, nil, AlertUnexpectedMessage
}
cert := &CertificateBody{}
_, err := cert.Unmarshal(hm.body)
if err != nil {
logf(logTypeHandshake, "[ServerStateWaitCert] Unexpected message")
return nil, nil, AlertDecodeError
}
state.handshakeHash.Write(hm.Marshal())
if len(cert.CertificateList) == 0 {
logf(logTypeHandshake, "[ServerStateWaitCert] WARNING client did not provide a certificate")
logf(logTypeHandshake, "[ServerStateWaitCert] -> [ServerStateWaitFinished]")
nextState := ServerStateWaitFinished{
Params: state.Params,
cryptoParams: state.cryptoParams,
masterSecret: state.masterSecret,
clientHandshakeTrafficSecret: state.clientHandshakeTrafficSecret,
handshakeHash: state.handshakeHash,
clientTrafficSecret: state.clientTrafficSecret,
serverTrafficSecret: state.serverTrafficSecret,
exporterSecret: state.exporterSecret,
}
return nextState, nil, AlertNoAlert
}
logf(logTypeHandshake, "[ServerStateWaitCert] -> [ServerStateWaitCV]")
nextState := ServerStateWaitCV{
AuthCertificate: state.AuthCertificate,
Params: state.Params,
cryptoParams: state.cryptoParams,
masterSecret: state.masterSecret,
clientHandshakeTrafficSecret: state.clientHandshakeTrafficSecret,
handshakeHash: state.handshakeHash,
clientTrafficSecret: state.clientTrafficSecret,
serverTrafficSecret: state.serverTrafficSecret,
clientCertificate: cert,
exporterSecret: state.exporterSecret,
}
return nextState, nil, AlertNoAlert
}
type ServerStateWaitCV struct {
AuthCertificate func(chain []CertificateEntry) error
Params ConnectionParameters
cryptoParams CipherSuiteParams
masterSecret []byte
clientHandshakeTrafficSecret []byte
handshakeHash hash.Hash
clientTrafficSecret []byte
serverTrafficSecret []byte
exporterSecret []byte
clientCertificate *CertificateBody
}
func (state ServerStateWaitCV) Next(hm *HandshakeMessage) (HandshakeState, []HandshakeAction, Alert) {
if hm == nil || hm.msgType != HandshakeTypeCertificateVerify {
logf(logTypeHandshake, "[ServerStateWaitCV] Unexpected message [%+v] [%s]", hm, reflect.TypeOf(hm))
return nil, nil, AlertUnexpectedMessage
}
certVerify := &CertificateVerifyBody{}
_, err := certVerify.Unmarshal(hm.body)
if err != nil {
logf(logTypeHandshake, "[ServerStateWaitCert] Error decoding message %v", err)
return nil, nil, AlertDecodeError
}
// Verify client signature over handshake hash
hcv := state.handshakeHash.Sum(nil)
logf(logTypeHandshake, "Handshake Hash to be verified: [%d] %x", len(hcv), hcv)
clientPublicKey := state.clientCertificate.CertificateList[0].CertData.PublicKey
if err := certVerify.Verify(clientPublicKey, hcv); err != nil {
logf(logTypeHandshake, "[ServerStateWaitCV] Failure in client auth verification [%v]", err)
return nil, nil, AlertHandshakeFailure
}
if state.AuthCertificate != nil {
err := state.AuthCertificate(state.clientCertificate.CertificateList)
if err != nil {
logf(logTypeHandshake, "[ServerStateWaitCV] Application rejected client certificate")
return nil, nil, AlertBadCertificate
}
} else {
logf(logTypeHandshake, "[ServerStateWaitCV] WARNING: No verification of client certificate")
}
// If it passes, record the certificateVerify in the transcript hash
state.handshakeHash.Write(hm.Marshal())
logf(logTypeHandshake, "[ServerStateWaitCV] -> [ServerStateWaitFinished]")
nextState := ServerStateWaitFinished{
Params: state.Params,
cryptoParams: state.cryptoParams,
masterSecret: state.masterSecret,
clientHandshakeTrafficSecret: state.clientHandshakeTrafficSecret,
handshakeHash: state.handshakeHash,
clientTrafficSecret: state.clientTrafficSecret,
serverTrafficSecret: state.serverTrafficSecret,
exporterSecret: state.exporterSecret,
}
return nextState, nil, AlertNoAlert
}
type ServerStateWaitFinished struct {
Params ConnectionParameters
cryptoParams CipherSuiteParams
masterSecret []byte
clientHandshakeTrafficSecret []byte
handshakeHash hash.Hash
clientTrafficSecret []byte
serverTrafficSecret []byte
exporterSecret []byte
}
func (state ServerStateWaitFinished) Next(hm *HandshakeMessage) (HandshakeState, []HandshakeAction, Alert) {
if hm == nil || hm.msgType != HandshakeTypeFinished {
logf(logTypeHandshake, "[ServerStateWaitFinished] Unexpected message")
return nil, nil, AlertUnexpectedMessage
}
fin := &FinishedBody{VerifyDataLen: state.cryptoParams.Hash.Size()}
_, err := fin.Unmarshal(hm.body)
if err != nil {
logf(logTypeHandshake, "[ServerStateWaitFinished] Error decoding message %v", err)
return nil, nil, AlertDecodeError
}
// Verify client Finished data
h5 := state.handshakeHash.Sum(nil)
logf(logTypeCrypto, "handshake hash for client Finished: [%d] %x", len(h5), h5)
clientFinishedData := computeFinishedData(state.cryptoParams, state.clientHandshakeTrafficSecret, h5)
logf(logTypeCrypto, "client Finished data: [%d] %x", len(clientFinishedData), clientFinishedData)
if !bytes.Equal(fin.VerifyData, clientFinishedData) {
logf(logTypeHandshake, "[ServerStateWaitFinished] Client's Finished failed to verify")
return nil, nil, AlertHandshakeFailure
}
// Compute the resumption secret
state.handshakeHash.Write(hm.Marshal())
h6 := state.handshakeHash.Sum(nil)
logf(logTypeCrypto, "handshake hash 6 [%d]: %x", len(h6), h6)
resumptionSecret := deriveSecret(state.cryptoParams, state.masterSecret, labelResumptionSecret, h6)
logf(logTypeCrypto, "resumption secret: [%d] %x", len(resumptionSecret), resumptionSecret)
// Compute client traffic keys
clientTrafficKeys := makeTrafficKeys(state.cryptoParams, state.clientTrafficSecret)
logf(logTypeHandshake, "[ServerStateWaitFinished] -> [StateConnected]")
nextState := StateConnected{
Params: state.Params,
isClient: false,
cryptoParams: state.cryptoParams,
resumptionSecret: resumptionSecret,
clientTrafficSecret: state.clientTrafficSecret,
serverTrafficSecret: state.serverTrafficSecret,
exporterSecret: state.exporterSecret,
}
toSend := []HandshakeAction{
RekeyIn{Label: "application", KeySet: clientTrafficKeys},
}
return nextState, toSend, AlertNoAlert
}

View File

@ -1,230 +0,0 @@
package mint
import (
"time"
)
// Marker interface for actions that an implementation should take based on
// state transitions.
type HandshakeAction interface{}
type SendHandshakeMessage struct {
Message *HandshakeMessage
}
type SendEarlyData struct{}
type ReadEarlyData struct{}
type ReadPastEarlyData struct{}
type RekeyIn struct {
Label string
KeySet keySet
}
type RekeyOut struct {
Label string
KeySet keySet
}
type StorePSK struct {
PSK PreSharedKey
}
type HandshakeState interface {
Next(hm *HandshakeMessage) (HandshakeState, []HandshakeAction, Alert)
}
type AppExtensionHandler interface {
Send(hs HandshakeType, el *ExtensionList) error
Receive(hs HandshakeType, el *ExtensionList) error
}
// Capabilities objects represent the capabilities of a TLS client or server,
// as an input to TLS negotiation
type Capabilities struct {
// For both client and server
CipherSuites []CipherSuite
Groups []NamedGroup
SignatureSchemes []SignatureScheme
PSKs PreSharedKeyCache
Certificates []*Certificate
AuthCertificate func(chain []CertificateEntry) error
ExtensionHandler AppExtensionHandler
// For client
PSKModes []PSKKeyExchangeMode
// For server
NextProtos []string
AllowEarlyData bool
RequireCookie bool
CookieHandler CookieHandler
RequireClientAuth bool
}
// ConnectionOptions objects represent per-connection settings for a client
// initiating a connection
type ConnectionOptions struct {
ServerName string
NextProtos []string
EarlyData []byte
}
// ConnectionParameters objects represent the parameters negotiated for a
// connection.
type ConnectionParameters struct {
UsingPSK bool
UsingDH bool
ClientSendingEarlyData bool
UsingEarlyData bool
UsingClientAuth bool
CipherSuite CipherSuite
ServerName string
NextProto string
}
// StateConnected is symmetric between client and server
type StateConnected struct {
Params ConnectionParameters
isClient bool
cryptoParams CipherSuiteParams
resumptionSecret []byte
clientTrafficSecret []byte
serverTrafficSecret []byte
exporterSecret []byte
}
func (state *StateConnected) KeyUpdate(request KeyUpdateRequest) ([]HandshakeAction, Alert) {
var trafficKeys keySet
if state.isClient {
state.clientTrafficSecret = HkdfExpandLabel(state.cryptoParams.Hash, state.clientTrafficSecret,
labelClientApplicationTrafficSecret, []byte{}, state.cryptoParams.Hash.Size())
trafficKeys = makeTrafficKeys(state.cryptoParams, state.clientTrafficSecret)
} else {
state.serverTrafficSecret = HkdfExpandLabel(state.cryptoParams.Hash, state.serverTrafficSecret,
labelServerApplicationTrafficSecret, []byte{}, state.cryptoParams.Hash.Size())
trafficKeys = makeTrafficKeys(state.cryptoParams, state.serverTrafficSecret)
}
kum, err := HandshakeMessageFromBody(&KeyUpdateBody{KeyUpdateRequest: request})
if err != nil {
logf(logTypeHandshake, "[StateConnected] Error marshaling key update message: %v", err)
return nil, AlertInternalError
}
toSend := []HandshakeAction{
SendHandshakeMessage{kum},
RekeyOut{Label: "update", KeySet: trafficKeys},
}
return toSend, AlertNoAlert
}
func (state *StateConnected) NewSessionTicket(length int, lifetime, earlyDataLifetime uint32) ([]HandshakeAction, Alert) {
tkt, err := NewSessionTicket(length, lifetime)
if err != nil {
logf(logTypeHandshake, "[StateConnected] Error generating NewSessionTicket: %v", err)
return nil, AlertInternalError
}
err = tkt.Extensions.Add(&TicketEarlyDataInfoExtension{earlyDataLifetime})
if err != nil {
logf(logTypeHandshake, "[StateConnected] Error adding extension to NewSessionTicket: %v", err)
return nil, AlertInternalError
}
resumptionKey := HkdfExpandLabel(state.cryptoParams.Hash, state.resumptionSecret,
labelResumption, tkt.TicketNonce, state.cryptoParams.Hash.Size())
newPSK := PreSharedKey{
CipherSuite: state.cryptoParams.Suite,
IsResumption: true,
Identity: tkt.Ticket,
Key: resumptionKey,
NextProto: state.Params.NextProto,
ReceivedAt: time.Now(),
ExpiresAt: time.Now().Add(time.Duration(tkt.TicketLifetime) * time.Second),
TicketAgeAdd: tkt.TicketAgeAdd,
}
tktm, err := HandshakeMessageFromBody(tkt)
if err != nil {
logf(logTypeHandshake, "[StateConnected] Error marshaling NewSessionTicket: %v", err)
return nil, AlertInternalError
}
toSend := []HandshakeAction{
StorePSK{newPSK},
SendHandshakeMessage{tktm},
}
return toSend, AlertNoAlert
}
func (state StateConnected) Next(hm *HandshakeMessage) (HandshakeState, []HandshakeAction, Alert) {
if hm == nil {
logf(logTypeHandshake, "[StateConnected] Unexpected message")
return nil, nil, AlertUnexpectedMessage
}
bodyGeneric, err := hm.ToBody()
if err != nil {
logf(logTypeHandshake, "[StateConnected] Error decoding message: %v", err)
return nil, nil, AlertDecodeError
}
switch body := bodyGeneric.(type) {
case *KeyUpdateBody:
var trafficKeys keySet
if !state.isClient {
state.clientTrafficSecret = HkdfExpandLabel(state.cryptoParams.Hash, state.clientTrafficSecret,
labelClientApplicationTrafficSecret, []byte{}, state.cryptoParams.Hash.Size())
trafficKeys = makeTrafficKeys(state.cryptoParams, state.clientTrafficSecret)
} else {
state.serverTrafficSecret = HkdfExpandLabel(state.cryptoParams.Hash, state.serverTrafficSecret,
labelServerApplicationTrafficSecret, []byte{}, state.cryptoParams.Hash.Size())
trafficKeys = makeTrafficKeys(state.cryptoParams, state.serverTrafficSecret)
}
toSend := []HandshakeAction{RekeyIn{Label: "update", KeySet: trafficKeys}}
// If requested, roll outbound keys and send a KeyUpdate
if body.KeyUpdateRequest == KeyUpdateRequested {
moreToSend, alert := state.KeyUpdate(KeyUpdateNotRequested)
if alert != AlertNoAlert {
return nil, nil, alert
}
toSend = append(toSend, moreToSend...)
}
return state, toSend, AlertNoAlert
case *NewSessionTicketBody:
// XXX: Allow NewSessionTicket in both directions?
if !state.isClient {
return nil, nil, AlertUnexpectedMessage
}
resumptionKey := HkdfExpandLabel(state.cryptoParams.Hash, state.resumptionSecret,
labelResumption, body.TicketNonce, state.cryptoParams.Hash.Size())
psk := PreSharedKey{
CipherSuite: state.cryptoParams.Suite,
IsResumption: true,
Identity: body.Ticket,
Key: resumptionKey,
NextProto: state.Params.NextProto,
ReceivedAt: time.Now(),
ExpiresAt: time.Now().Add(time.Duration(body.TicketLifetime) * time.Second),
TicketAgeAdd: body.TicketAgeAdd,
}
toSend := []HandshakeAction{StorePSK{psk}}
return state, toSend, AlertNoAlert
}
logf(logTypeHandshake, "[StateConnected] Unexpected message type %v", hm.msgType)
return nil, nil, AlertUnexpectedMessage
}

View File

@ -1,74 +0,0 @@
TLS Syntax
==========
TLS defines [its own syntax](https://tlswg.github.io/tls13-spec/#rfc.section.3)
for describing structures used in that protocol. To facilitate experimentation
with TLS in Go, this module maps that syntax to the Go structure syntax, taking
advantage of Go's type annotations to encode non-type information carried in the
TLS presentation format.
For example, in the TLS specification, a ClientHello message has the following
structure:
~~~~~
uint16 ProtocolVersion;
opaque Random[32];
uint8 CipherSuite[2];
enum { ... (65535)} ExtensionType;
struct {
ExtensionType extension_type;
opaque extension_data<0..2^16-1>;
} Extension;
struct {
ProtocolVersion legacy_version = 0x0303; /* TLS v1.2 */
Random random;
opaque legacy_session_id<0..32>;
CipherSuite cipher_suites<2..2^16-2>;
opaque legacy_compression_methods<1..2^8-1>;
Extension extensions<0..2^16-1>;
} ClientHello;
~~~~~
This maps to the following Go type definitions:
~~~~~
type protocolVersion uint16
type random [32]byte
type cipherSuite uint16 // or [2]byte
type extensionType uint16
type extension struct {
ExtensionType extensionType
ExtensionData []byte `tls:"head=2"`
}
type clientHello struct {
LegacyVersion protocolVersion
Random random
LegacySessionID []byte `tls:"head=1,max=32"`
CipherSuites []cipherSuite `tls:"head=2,min=2"`
LegacyCompressionMethods []byte `tls:"head=1,min=1"`
Extensions []extension `tls:"head=2"`
}
~~~~~
Then you can just declare, marshal, and unmarshal structs just like you would
with, say JSON.
The available annotations right now are all related to vectors:
* `head`: The number of bytes of length to use as a "header"
* `min`: The minimum length of the vector, in bytes
* `max`: The maximum length of the vector, in bytes
## Not supported
* The `select()` syntax for creating alternate version of the same struct (see,
e.g., the KeyShare extension)
* The backreference syntax for array lengths or select parameters, as in `opaque
fragment[TLSPlaintext.length]`. Note, however, that in cases where the length
immediately preceds the array, these can be reframed as vectors with
appropriate sizes.

View File

@ -1,243 +0,0 @@
package syntax
import (
"bytes"
"fmt"
"reflect"
"runtime"
)
func Unmarshal(data []byte, v interface{}) (int, error) {
// Check for well-formedness.
// Avoids filling out half a data structure
// before discovering a JSON syntax error.
d := decodeState{}
d.Write(data)
return d.unmarshal(v)
}
// These are the options that can be specified in the struct tag. Right now,
// all of them apply to variable-length vectors and nothing else
type decOpts struct {
head uint // length of length in bytes
min uint // minimum size in bytes
max uint // maximum size in bytes
}
type decodeState struct {
bytes.Buffer
}
func (d *decodeState) unmarshal(v interface{}) (read int, err error) {
defer func() {
if r := recover(); r != nil {
if _, ok := r.(runtime.Error); ok {
panic(r)
}
if s, ok := r.(string); ok {
panic(s)
}
err = r.(error)
}
}()
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Ptr || rv.IsNil() {
return 0, fmt.Errorf("Invalid unmarshal target (non-pointer or nil)")
}
read = d.value(rv)
return read, nil
}
func (e *decodeState) value(v reflect.Value) int {
return valueDecoder(v)(e, v, decOpts{})
}
type decoderFunc func(e *decodeState, v reflect.Value, opts decOpts) int
func valueDecoder(v reflect.Value) decoderFunc {
return typeDecoder(v.Type().Elem())
}
func typeDecoder(t reflect.Type) decoderFunc {
// Note: Omits the caching / wait-group things that encoding/json uses
return newTypeDecoder(t)
}
func newTypeDecoder(t reflect.Type) decoderFunc {
// Note: Does not support Marshaler, so don't need the allowAddr argument
switch t.Kind() {
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return uintDecoder
case reflect.Array:
return newArrayDecoder(t)
case reflect.Slice:
return newSliceDecoder(t)
case reflect.Struct:
return newStructDecoder(t)
default:
panic(fmt.Errorf("Unsupported type (%s)", t))
}
}
///// Specific decoders below
func uintDecoder(d *decodeState, v reflect.Value, opts decOpts) int {
var uintLen int
switch v.Elem().Kind() {
case reflect.Uint8:
uintLen = 1
case reflect.Uint16:
uintLen = 2
case reflect.Uint32:
uintLen = 4
case reflect.Uint64:
uintLen = 8
}
buf := make([]byte, uintLen)
n, err := d.Read(buf)
if err != nil {
panic(err)
}
if n != uintLen {
panic(fmt.Errorf("Insufficient data to read uint"))
}
val := uint64(0)
for _, b := range buf {
val = (val << 8) + uint64(b)
}
v.Elem().SetUint(val)
return uintLen
}
//////////
type arrayDecoder struct {
elemDec decoderFunc
}
func (ad *arrayDecoder) decode(d *decodeState, v reflect.Value, opts decOpts) int {
n := v.Elem().Type().Len()
read := 0
for i := 0; i < n; i += 1 {
read += ad.elemDec(d, v.Elem().Index(i).Addr(), opts)
}
return read
}
func newArrayDecoder(t reflect.Type) decoderFunc {
dec := &arrayDecoder{typeDecoder(t.Elem())}
return dec.decode
}
//////////
type sliceDecoder struct {
elementType reflect.Type
elementDec decoderFunc
}
func (sd *sliceDecoder) decode(d *decodeState, v reflect.Value, opts decOpts) int {
if opts.head == 0 {
panic(fmt.Errorf("Cannot decode a slice without a header length"))
}
lengthBytes := make([]byte, opts.head)
n, err := d.Read(lengthBytes)
if err != nil {
panic(err)
}
if uint(n) != opts.head {
panic(fmt.Errorf("Not enough data to read header"))
}
length := uint(0)
for _, b := range lengthBytes {
length = (length << 8) + uint(b)
}
if opts.max > 0 && length > opts.max {
panic(fmt.Errorf("Length of vector exceeds declared max"))
}
if length < opts.min {
panic(fmt.Errorf("Length of vector below declared min"))
}
data := make([]byte, length)
n, err = d.Read(data)
if err != nil {
panic(err)
}
if uint(n) != length {
panic(fmt.Errorf("Available data less than declared length [%04x < %04x]", n, length))
}
elemBuf := &decodeState{}
elemBuf.Write(data)
elems := []reflect.Value{}
read := int(opts.head)
for elemBuf.Len() > 0 {
elem := reflect.New(sd.elementType)
read += sd.elementDec(elemBuf, elem, opts)
elems = append(elems, elem)
}
v.Elem().Set(reflect.MakeSlice(v.Elem().Type(), len(elems), len(elems)))
for i := 0; i < len(elems); i += 1 {
v.Elem().Index(i).Set(elems[i].Elem())
}
return read
}
func newSliceDecoder(t reflect.Type) decoderFunc {
dec := &sliceDecoder{
elementType: t.Elem(),
elementDec: typeDecoder(t.Elem()),
}
return dec.decode
}
//////////
type structDecoder struct {
fieldOpts []decOpts
fieldDecs []decoderFunc
}
func (sd *structDecoder) decode(d *decodeState, v reflect.Value, opts decOpts) int {
read := 0
for i := range sd.fieldDecs {
read += sd.fieldDecs[i](d, v.Elem().Field(i).Addr(), sd.fieldOpts[i])
}
return read
}
func newStructDecoder(t reflect.Type) decoderFunc {
n := t.NumField()
sd := structDecoder{
fieldOpts: make([]decOpts, n),
fieldDecs: make([]decoderFunc, n),
}
for i := 0; i < n; i += 1 {
f := t.Field(i)
tag := f.Tag.Get("tls")
tagOpts := parseTag(tag)
sd.fieldOpts[i] = decOpts{
head: tagOpts["head"],
max: tagOpts["max"],
min: tagOpts["min"],
}
sd.fieldDecs[i] = typeDecoder(f.Type)
}
return sd.decode
}

View File

@ -1,187 +0,0 @@
package syntax
import (
"bytes"
"fmt"
"reflect"
"runtime"
)
func Marshal(v interface{}) ([]byte, error) {
e := &encodeState{}
err := e.marshal(v, encOpts{})
if err != nil {
return nil, err
}
return e.Bytes(), nil
}
// These are the options that can be specified in the struct tag. Right now,
// all of them apply to variable-length vectors and nothing else
type encOpts struct {
head uint // length of length in bytes
min uint // minimum size in bytes
max uint // maximum size in bytes
}
type encodeState struct {
bytes.Buffer
}
func (e *encodeState) marshal(v interface{}, opts encOpts) (err error) {
defer func() {
if r := recover(); r != nil {
if _, ok := r.(runtime.Error); ok {
panic(r)
}
if s, ok := r.(string); ok {
panic(s)
}
err = r.(error)
}
}()
e.reflectValue(reflect.ValueOf(v), opts)
return nil
}
func (e *encodeState) reflectValue(v reflect.Value, opts encOpts) {
valueEncoder(v)(e, v, opts)
}
type encoderFunc func(e *encodeState, v reflect.Value, opts encOpts)
func valueEncoder(v reflect.Value) encoderFunc {
if !v.IsValid() {
panic(fmt.Errorf("Cannot encode an invalid value"))
}
return typeEncoder(v.Type())
}
func typeEncoder(t reflect.Type) encoderFunc {
// Note: Omits the caching / wait-group things that encoding/json uses
return newTypeEncoder(t)
}
func newTypeEncoder(t reflect.Type) encoderFunc {
// Note: Does not support Marshaler, so don't need the allowAddr argument
switch t.Kind() {
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return uintEncoder
case reflect.Array:
return newArrayEncoder(t)
case reflect.Slice:
return newSliceEncoder(t)
case reflect.Struct:
return newStructEncoder(t)
default:
panic(fmt.Errorf("Unsupported type (%s)", t))
}
}
///// Specific encoders below
func uintEncoder(e *encodeState, v reflect.Value, opts encOpts) {
u := v.Uint()
switch v.Type().Kind() {
case reflect.Uint8:
e.WriteByte(byte(u))
case reflect.Uint16:
e.Write([]byte{byte(u >> 8), byte(u)})
case reflect.Uint32:
e.Write([]byte{byte(u >> 24), byte(u >> 16), byte(u >> 8), byte(u)})
case reflect.Uint64:
e.Write([]byte{byte(u >> 56), byte(u >> 48), byte(u >> 40), byte(u >> 32),
byte(u >> 24), byte(u >> 16), byte(u >> 8), byte(u)})
}
}
//////////
type arrayEncoder struct {
elemEnc encoderFunc
}
func (ae *arrayEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
n := v.Len()
for i := 0; i < n; i += 1 {
ae.elemEnc(e, v.Index(i), opts)
}
}
func newArrayEncoder(t reflect.Type) encoderFunc {
enc := &arrayEncoder{typeEncoder(t.Elem())}
return enc.encode
}
//////////
type sliceEncoder struct {
ae *arrayEncoder
}
func (se *sliceEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
if opts.head == 0 {
panic(fmt.Errorf("Cannot encode a slice without a header length"))
}
arrayState := &encodeState{}
se.ae.encode(arrayState, v, opts)
n := uint(arrayState.Len())
if opts.max > 0 && n > opts.max {
panic(fmt.Errorf("Encoded length more than max [%d > %d]", n, opts.max))
}
if n>>(8*opts.head) > 0 {
panic(fmt.Errorf("Encoded length too long for header length [%d, %d]", n, opts.head))
}
if n < opts.min {
panic(fmt.Errorf("Encoded length less than min [%d < %d]", n, opts.min))
}
for i := int(opts.head - 1); i >= 0; i -= 1 {
e.WriteByte(byte(n >> (8 * uint(i))))
}
e.Write(arrayState.Bytes())
}
func newSliceEncoder(t reflect.Type) encoderFunc {
enc := &sliceEncoder{&arrayEncoder{typeEncoder(t.Elem())}}
return enc.encode
}
//////////
type structEncoder struct {
fieldOpts []encOpts
fieldEncs []encoderFunc
}
func (se *structEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
for i := range se.fieldEncs {
se.fieldEncs[i](e, v.Field(i), se.fieldOpts[i])
}
}
func newStructEncoder(t reflect.Type) encoderFunc {
n := t.NumField()
se := structEncoder{
fieldOpts: make([]encOpts, n),
fieldEncs: make([]encoderFunc, n),
}
for i := 0; i < n; i += 1 {
f := t.Field(i)
tag := f.Tag.Get("tls")
tagOpts := parseTag(tag)
se.fieldOpts[i] = encOpts{
head: tagOpts["head"],
max: tagOpts["max"],
min: tagOpts["min"],
}
se.fieldEncs[i] = typeEncoder(f.Type)
}
return se.encode
}

View File

@ -1,30 +0,0 @@
package syntax
import (
"strconv"
"strings"
)
// `tls:"head=2,min=2,max=255"`
type tagOptions map[string]uint
// parseTag parses a struct field's "tls" tag as a comma-separated list of
// name=value pairs, where the values MUST be unsigned integers
func parseTag(tag string) tagOptions {
opts := tagOptions{}
for _, token := range strings.Split(tag, ",") {
if strings.Index(token, "=") == -1 {
continue
}
parts := strings.Split(token, "=")
if len(parts[0]) == 0 {
continue
}
if val, err := strconv.Atoi(parts[1]); err == nil && val >= 0 {
opts[parts[0]] = uint(val)
}
}
return opts
}

View File

@ -1,168 +0,0 @@
package mint
// XXX(rlb): This file is borrowed pretty much wholesale from crypto/tls
import (
"errors"
"net"
"strings"
"time"
)
// Server returns a new TLS server side connection
// using conn as the underlying transport.
// The configuration config must be non-nil and must include
// at least one certificate or else set GetCertificate.
func Server(conn net.Conn, config *Config) *Conn {
return NewConn(conn, config, false)
}
// Client returns a new TLS client side connection
// using conn as the underlying transport.
// The config cannot be nil: users must set either ServerName or
// InsecureSkipVerify in the config.
func Client(conn net.Conn, config *Config) *Conn {
return NewConn(conn, config, true)
}
// A listener implements a network listener (net.Listener) for TLS connections.
type Listener struct {
net.Listener
config *Config
}
// Accept waits for and returns the next incoming TLS connection.
// The returned connection c is a *tls.Conn.
func (l *Listener) Accept() (c net.Conn, err error) {
c, err = l.Listener.Accept()
if err != nil {
return
}
server := Server(c, l.config)
err = server.Handshake()
if err == AlertNoAlert {
err = nil
}
c = server
return
}
// NewListener creates a Listener which accepts connections from an inner
// Listener and wraps each connection with Server.
// The configuration config must be non-nil and must include
// at least one certificate or else set GetCertificate.
func NewListener(inner net.Listener, config *Config) net.Listener {
l := new(Listener)
l.Listener = inner
l.config = config
return l
}
// Listen creates a TLS listener accepting connections on the
// given network address using net.Listen.
// The configuration config must be non-nil and must include
// at least one certificate or else set GetCertificate.
func Listen(network, laddr string, config *Config) (net.Listener, error) {
if config == nil || !config.ValidForServer() {
return nil, errors.New("tls: neither Certificates nor GetCertificate set in Config")
}
l, err := net.Listen(network, laddr)
if err != nil {
return nil, err
}
return NewListener(l, config), nil
}
type TimeoutError struct{}
func (TimeoutError) Error() string { return "tls: DialWithDialer timed out" }
func (TimeoutError) Timeout() bool { return true }
func (TimeoutError) Temporary() bool { return true }
// DialWithDialer connects to the given network address using dialer.Dial and
// then initiates a TLS handshake, returning the resulting TLS connection. Any
// timeout or deadline given in the dialer apply to connection and TLS
// handshake as a whole.
//
// DialWithDialer interprets a nil configuration as equivalent to the zero
// configuration; see the documentation of Config for the defaults.
func DialWithDialer(dialer *net.Dialer, network, addr string, config *Config) (*Conn, error) {
// We want the Timeout and Deadline values from dialer to cover the
// whole process: TCP connection and TLS handshake. This means that we
// also need to start our own timers now.
timeout := dialer.Timeout
if !dialer.Deadline.IsZero() {
deadlineTimeout := dialer.Deadline.Sub(time.Now())
if timeout == 0 || deadlineTimeout < timeout {
timeout = deadlineTimeout
}
}
var errChannel chan error
if timeout != 0 {
errChannel = make(chan error, 2)
time.AfterFunc(timeout, func() {
errChannel <- TimeoutError{}
})
}
rawConn, err := dialer.Dial(network, addr)
if err != nil {
return nil, err
}
colonPos := strings.LastIndex(addr, ":")
if colonPos == -1 {
colonPos = len(addr)
}
hostname := addr[:colonPos]
if config == nil {
config = &Config{}
}
// If no ServerName is set, infer the ServerName
// from the hostname we're connecting to.
if config.ServerName == "" {
// Make a copy to avoid polluting argument or default.
c := config.Clone()
c.ServerName = hostname
config = c
}
conn := Client(rawConn, config)
if timeout == 0 {
err = conn.Handshake()
if err == AlertNoAlert {
err = nil
}
} else {
go func() {
errChannel <- conn.Handshake()
}()
err = <-errChannel
if err == AlertNoAlert {
err = nil
}
}
if err != nil {
rawConn.Close()
return nil, err
}
return conn, nil
}
// Dial connects to the given network address using net.Dial
// and then initiates a TLS handshake, returning the resulting
// TLS connection.
// Dial interprets a nil configuration as equivalent to
// the zero configuration; see the documentation of Config
// for the defaults.
func Dial(network, addr string, config *Config) (*Conn, error) {
return DialWithDialer(new(net.Dialer), network, addr, config)
}

View File

@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2014 cheekybits
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,2 +0,0 @@
// Package generic contains the generic marker types.
package generic

View File

@ -1,13 +0,0 @@
package generic
// Type is the placeholder type that indicates a generic value.
// When genny is executed, variables of this type will be replaced with
// references to the specific types.
// var GenericType generic.Type
type Type interface{}
// Number is the placehoder type that indiccates a generic numerical value.
// When genny is executed, variables of this type will be replaced with
// references to the specific types.
// var GenericType generic.Number
type Number float64

View File

@ -1,69 +0,0 @@
SipHash (Go)
============
[![Build Status](https://travis-ci.org/dchest/siphash.svg)](https://travis-ci.org/dchest/siphash)
Go implementation of SipHash-2-4, a fast short-input PRF created by
Jean-Philippe Aumasson and Daniel J. Bernstein (http://131002.net/siphash/).
## Installation
$ go get github.com/dchest/siphash
## Usage
import "github.com/dchest/siphash"
There are two ways to use this package.
The slower one is to use the standard hash.Hash64 interface:
h := siphash.New(key)
h.Write([]byte("Hello"))
sum := h.Sum(nil) // returns 8-byte []byte
or
sum64 := h.Sum64() // returns uint64
The faster one is to use Hash() function, which takes two uint64 parts of
16-byte key and a byte slice, and returns uint64 hash:
sum64 := siphash.Hash(key0, key1, []byte("Hello"))
The keys and output are little-endian.
## Functions
### func Hash(k0, k1 uint64, p []byte) uint64
Hash returns the 64-bit SipHash-2-4 of the given byte slice with two
64-bit parts of 128-bit key: k0 and k1.
### func Hash128(k0, k1 uint64, p []byte) (uint64, uint64)
Hash128 returns the 128-bit SipHash-2-4 of the given byte slice with two
64-bit parts of 128-bit key: k0 and k1.
Note that 128-bit SipHash is considered experimental by SipHash authors at this time.
### func New(key []byte) hash.Hash64
New returns a new hash.Hash64 computing SipHash-2-4 with 16-byte key.
### func New128(key []byte) hash.Hash
New128 returns a new hash.Hash computing SipHash-2-4 with 16-byte key and 16-byte output.
Note that 16-byte output is considered experimental by SipHash authors at this time.
## Public domain dedication
Written by Dmitry Chestnykh and Damian Gryski.
To the extent possible under law, the authors have dedicated all copyright
and related and neighboring rights to this software to the public domain
worldwide. This software is distributed without any warranty.
http://creativecommons.org/publicdomain/zero/1.0/

View File

@ -1,148 +0,0 @@
// +build !arm,!amd64 appengine gccgo
package siphash
func once(d *digest) {
blocks(d, d.x[:])
}
func finalize(d *digest) uint64 {
d0 := *d
once(&d0)
v0, v1, v2, v3 := d0.v0, d0.v1, d0.v2, d0.v3
v2 ^= 0xff
// Round 1.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 2.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 3.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 4.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
return v0 ^ v1 ^ v2 ^ v3
}
func blocks(d *digest, p []uint8) {
v0, v1, v2, v3 := d.v0, d.v1, d.v2, d.v3
for len(p) >= BlockSize {
m := uint64(p[0]) | uint64(p[1])<<8 | uint64(p[2])<<16 | uint64(p[3])<<24 |
uint64(p[4])<<32 | uint64(p[5])<<40 | uint64(p[6])<<48 | uint64(p[7])<<56
v3 ^= m
// Round 1.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 2.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
v0 ^= m
p = p[BlockSize:]
}
d.v0, d.v1, d.v2, d.v3 = v0, v1, v2, v3
}

View File

@ -1,86 +0,0 @@
// +build amd64,!appengine,!gccgo
#define ROUND(v0, v1, v2, v3) \
ADDQ v1, v0; \
RORQ $51, v1; \
ADDQ v3, v2; \
XORQ v0, v1; \
RORQ $48, v3; \
RORQ $32, v0; \
XORQ v2, v3; \
ADDQ v1, v2; \
ADDQ v3, v0; \
RORQ $43, v3; \
RORQ $47, v1; \
XORQ v0, v3; \
XORQ v2, v1; \
RORQ $32, v2
// blocks(d *digest, data []uint8)
TEXT ·blocks(SB),4,$0-32
MOVQ d+0(FP), BX
MOVQ 0(BX), R9 // R9 = v0
MOVQ 8(BX), R10 // R10 = v1
MOVQ 16(BX), R11 // R11 = v2
MOVQ 24(BX), R12 // R12 = v3
MOVQ p_base+8(FP), DI // DI = *uint64
MOVQ p_len+16(FP), SI // SI = nblocks
XORL DX, DX // DX = index (0)
SHRQ $3, SI // SI /= 8
body:
CMPQ DX, SI
JGE end
MOVQ 0(DI)(DX*8), CX // CX = m
XORQ CX, R12
ROUND(R9, R10, R11, R12)
ROUND(R9, R10, R11, R12)
XORQ CX, R9
ADDQ $1, DX
JMP body
end:
MOVQ R9, 0(BX)
MOVQ R10, 8(BX)
MOVQ R11, 16(BX)
MOVQ R12, 24(BX)
RET
// once(d *digest)
TEXT ·once(SB),4,$0-8
MOVQ d+0(FP), BX
MOVQ 0(BX), R9 // R9 = v0
MOVQ 8(BX), R10 // R10 = v1
MOVQ 16(BX), R11 // R11 = v2
MOVQ 24(BX), R12 // R12 = v3
MOVQ 48(BX), CX // CX = d.x[:]
XORQ CX, R12
ROUND(R9, R10, R11, R12)
ROUND(R9, R10, R11, R12)
XORQ CX, R9
MOVQ R9, 0(BX)
MOVQ R10, 8(BX)
MOVQ R11, 16(BX)
MOVQ R12, 24(BX)
RET
// finalize(d *digest) uint64
TEXT ·finalize(SB),4,$0-16
MOVQ d+0(FP), BX
MOVQ 0(BX), R9 // R9 = v0
MOVQ 8(BX), R10 // R10 = v1
MOVQ 16(BX), R11 // R11 = v2
MOVQ 24(BX), R12 // R12 = v3
MOVQ 48(BX), CX // CX = d.x[:]
XORQ CX, R12
ROUND(R9, R10, R11, R12)
ROUND(R9, R10, R11, R12)
XORQ CX, R9
NOTB R11
ROUND(R9, R10, R11, R12)
ROUND(R9, R10, R11, R12)
ROUND(R9, R10, R11, R12)
ROUND(R9, R10, R11, R12)
XORQ R12, R11
XORQ R10, R9
XORQ R11, R9
MOVQ R9, ret+8(FP)
RET

View File

@ -1,144 +0,0 @@
#include "textflag.h"
#define R10 g
#define ROUND()\
ADD.S R2,R0,R0;\
ADC R3,R1,R1;\
EOR R2<<13,R0,R8;\
EOR R3>>19,R8,R8;\
EOR R2>>19,R1,R11;\
EOR R3<<13,R11,R11;\
ADD.S R6,R4,R4;\
ADC R7,R5,R5;\
EOR R6<<16,R4,R2;\
EOR R7>>16,R2,R2;\
EOR R6>>16,R5,R3;\
EOR R7<<16,R3,R3;\
ADD.S R2,R1,R1;\
ADC R3,R0,R0;\
EOR R2<<21,R1,R6;\
EOR R3>>11,R6,R6;\
EOR R2>>11,R0,R7;\
EOR R3<<21,R7,R7;\
ADD.S R8,R4,R4;\
ADC R11,R5,R5;\
EOR R8<<17,R4,R2;\
EOR R11>>15,R2,R2;\
EOR R8>>15,R5,R3;\
EOR R11<<17,R3,R3;\
ADD.S R2,R1,R1;\
ADC R3,R0,R0;\
EOR R2<<13,R1,R8;\
EOR R3>>19,R8,R8;\
EOR R2>>19,R0,R11;\
EOR R3<<13,R11,R11;\
ADD.S R6,R5,R5;\
ADC R7,R4,R4;\
EOR R6<<16,R5,R2;\
EOR R7>>16,R2,R2;\
EOR R6>>16,R4,R3;\
EOR R7<<16,R3,R3;\
ADD.S R2,R0,R0;\
ADC R3,R1,R1;\
EOR R2<<21,R0,R6;\
EOR R3>>11,R6,R6;\
EOR R2>>11,R1,R7;\
EOR R3<<21,R7,R7;\
ADD.S R8,R5,R5;\
ADC R11,R4,R4;\
EOR R8<<17,R5,R2;\
EOR R11>>15,R2,R2;\
EOR R8>>15,R4,R3;\
EOR R11<<17,R3,R3;\
// once(d *digest)
TEXT ·once(SB),NOSPLIT,$4-4
MOVW d+0(FP),R8
MOVM.IA (R8),[R0,R1,R2,R3,R4,R5,R6,R7]
MOVW 48(R8),R12
MOVW 52(R8),R14
EOR R12,R6,R6
EOR R14,R7,R7
ROUND()
EOR R12,R0,R0
EOR R14,R1,R1
MOVW d+0(FP),R8
MOVM.IA [R0,R1,R2,R3,R4,R5,R6,R7],(R8)
RET
// finalize(d *digest) uint64
TEXT ·finalize(SB),NOSPLIT,$4-12
MOVW d+0(FP),R8
MOVM.IA (R8),[R0,R1,R2,R3,R4,R5,R6,R7]
MOVW 48(R8),R12
MOVW 52(R8),R14
EOR R12,R6,R6
EOR R14,R7,R7
ROUND()
EOR R12,R0,R0
EOR R14,R1,R1
EOR $255,R4
ROUND()
ROUND()
EOR R2,R0,R0
EOR R3,R1,R1
EOR R6,R4,R4
EOR R7,R5,R5
EOR R4,R0,R0
EOR R5,R1,R1
MOVW R0,ret_lo+4(FP)
MOVW R1,ret_hi+8(FP)
RET
// blocks(d *digest, data []uint8)
TEXT ·blocks(SB),NOSPLIT,$8-16
MOVW R10,sav-8(SP)
MOVW d+0(FP),R8
MOVM.IA (R8),[R0,R1,R2,R3,R4,R5,R6,R7]
MOVW p+4(FP),R10
MOVW p_len+8(FP),R11
ADD R10,R11,R11
MOVW R11,endp-4(SP)
AND.S $3,R10,R8
BNE blocksunaligned
blocksloop:
MOVM.IA.W (R10),[R12,R14]
EOR R12,R6,R6
EOR R14,R7,R7
ROUND()
EOR R12,R0,R0
EOR R14,R1,R1
MOVW endp-4(SP),R11
CMP R11,R10
BLO blocksloop
MOVW d+0(FP),R8
MOVM.IA [R0,R1,R2,R3,R4,R5,R6,R7],(R8)
MOVW sav-8(SP),R10
RET
blocksunaligned:
MOVB (R10),R12
MOVB 1(R10),R11
ORR R11<<8,R12,R12
MOVB 2(R10),R11
ORR R11<<16,R12,R12
MOVB 3(R10),R11
ORR R11<<24,R12,R12
MOVB 4(R10),R14
MOVB 5(R10),R11
ORR R11<<8,R14,R14
MOVB 6(R10),R11
ORR R11<<16,R14,R14
MOVB 7(R10),R11
ORR R11<<24,R14,R14
ADD $8,R10,R10
EOR R12,R6,R6
EOR R14,R7,R7
ROUND()
EOR R12,R0,R0
EOR R14,R1,R1
MOVW endp-4(SP),R11
CMP R11,R10
BLO blocksunaligned
MOVW d+0(FP),R8
MOVM.IA [R0,R1,R2,R3,R4,R5,R6,R7],(R8)
MOVW sav-8(SP),R10
RET

View File

@ -1,216 +0,0 @@
// +build !arm,!amd64 appengine gccgo
// Written in 2012 by Dmitry Chestnykh.
//
// To the extent possible under law, the author have dedicated all copyright
// and related and neighboring rights to this software to the public domain
// worldwide. This software is distributed without any warranty.
// http://creativecommons.org/publicdomain/zero/1.0/
package siphash
// Hash returns the 64-bit SipHash-2-4 of the given byte slice with two 64-bit
// parts of 128-bit key: k0 and k1.
func Hash(k0, k1 uint64, p []byte) uint64 {
// Initialization.
v0 := k0 ^ 0x736f6d6570736575
v1 := k1 ^ 0x646f72616e646f6d
v2 := k0 ^ 0x6c7967656e657261
v3 := k1 ^ 0x7465646279746573
t := uint64(len(p)) << 56
// Compression.
for len(p) >= BlockSize {
m := uint64(p[0]) | uint64(p[1])<<8 | uint64(p[2])<<16 | uint64(p[3])<<24 |
uint64(p[4])<<32 | uint64(p[5])<<40 | uint64(p[6])<<48 | uint64(p[7])<<56
v3 ^= m
// Round 1.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 2.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
v0 ^= m
p = p[BlockSize:]
}
// Compress last block.
switch len(p) {
case 7:
t |= uint64(p[6]) << 48
fallthrough
case 6:
t |= uint64(p[5]) << 40
fallthrough
case 5:
t |= uint64(p[4]) << 32
fallthrough
case 4:
t |= uint64(p[3]) << 24
fallthrough
case 3:
t |= uint64(p[2]) << 16
fallthrough
case 2:
t |= uint64(p[1]) << 8
fallthrough
case 1:
t |= uint64(p[0])
}
v3 ^= t
// Round 1.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 2.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
v0 ^= t
// Finalization.
v2 ^= 0xff
// Round 1.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 2.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 3.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 4.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
return v0 ^ v1 ^ v2 ^ v3
}

View File

@ -1,302 +0,0 @@
// +build !arm,!amd64 appengine gccgo
// Written in 2012 by Dmitry Chestnykh.
// Modifications 2014 for 128-bit hash function by Damian Gryski.
//
// To the extent possible under law, the authors have dedicated all copyright
// and related and neighboring rights to this software to the public domain
// worldwide. This software is distributed without any warranty.
// http://creativecommons.org/publicdomain/zero/1.0/
package siphash
// Hash returns the 128-bit SipHash-2-4 of the given byte slice with two 64-bit
// parts of 128-bit key: k0 and k1.
//
// Note that 128-bit SipHash is considered experimental by SipHash authors at this time.
func Hash128(k0, k1 uint64, p []byte) (uint64, uint64) {
// Initialization.
v0 := k0 ^ 0x736f6d6570736575
v1 := k1 ^ 0x646f72616e646f6d
v2 := k0 ^ 0x6c7967656e657261
v3 := k1 ^ 0x7465646279746573
t := uint64(len(p)) << 56
v1 ^= 0xee
// Compression.
for len(p) >= BlockSize {
m := uint64(p[0]) | uint64(p[1])<<8 | uint64(p[2])<<16 | uint64(p[3])<<24 |
uint64(p[4])<<32 | uint64(p[5])<<40 | uint64(p[6])<<48 | uint64(p[7])<<56
v3 ^= m
// Round 1.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 2.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
v0 ^= m
p = p[BlockSize:]
}
// Compress last block.
switch len(p) {
case 7:
t |= uint64(p[6]) << 48
fallthrough
case 6:
t |= uint64(p[5]) << 40
fallthrough
case 5:
t |= uint64(p[4]) << 32
fallthrough
case 4:
t |= uint64(p[3]) << 24
fallthrough
case 3:
t |= uint64(p[2]) << 16
fallthrough
case 2:
t |= uint64(p[1]) << 8
fallthrough
case 1:
t |= uint64(p[0])
}
v3 ^= t
// Round 1.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 2.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
v0 ^= t
// Finalization.
v2 ^= 0xee
// Round 1.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 2.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 3.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 4.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
r0 := v0 ^ v1 ^ v2 ^ v3
v1 ^= 0xdd
// Round 1.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 2.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 3.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 4.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
r1 := v0 ^ v1 ^ v2 ^ v3
return r0, r1
}

View File

@ -1,292 +0,0 @@
// +build amd64,!appengine,!gccgo
// This is a translation of the gcc output of FloodyBerry's pure-C public
// domain siphash implementation at https://github.com/floodyberry/siphash
// This assembly code has been modified from the 64-bit output to the experiment 128-bit output.
// SI = v0
// AX = v1
// CX = v2
// DX = v3
// func Hash128(k0, k1 uint64, b []byte) (r0 uint64, r1 uint64)
TEXT ·Hash128(SB),4,$0-56
MOVQ k0+0(FP),CX
MOVQ $0x736F6D6570736575,R9
MOVQ k1+8(FP),DI
MOVQ $0x6C7967656E657261,BX
MOVQ $0x646F72616E646F6D,AX
MOVQ b_len+24(FP),DX
XORQ $0xEE,AX
MOVQ DX,R11
MOVQ DX,R10
XORQ CX,R9
XORQ CX,BX
MOVQ $0x7465646279746573,CX
XORQ DI,AX
XORQ DI,CX
SHLQ $0x38,R11
XORQ DI,DI
MOVQ b_base+16(FP),SI
ANDQ $0xFFFFFFFFFFFFFFF8,R10
JE afterLoop
XCHGQ AX,AX
loopBody:
MOVQ 0(SI)(DI*1),R8
ADDQ AX,R9
RORQ $0x33,AX
XORQ R9,AX
RORQ $0x20,R9
ADDQ $0x8,DI
XORQ R8,CX
ADDQ CX,BX
RORQ $0x30,CX
XORQ BX,CX
ADDQ AX,BX
RORQ $0x2F,AX
ADDQ CX,R9
RORQ $0x2B,CX
XORQ BX,AX
XORQ R9,CX
RORQ $0x20,BX
ADDQ AX,R9
ADDQ CX,BX
RORQ $0x33,AX
RORQ $0x30,CX
XORQ R9,AX
XORQ BX,CX
RORQ $0x20,R9
ADDQ AX,BX
ADDQ CX,R9
RORQ $0x2F,AX
RORQ $0x2B,CX
XORQ BX,AX
RORQ $0x20,BX
XORQ R9,CX
XORQ R8,R9
CMPQ R10,DI
JA loopBody
afterLoop:
SUBQ R10,DX
CMPQ DX,$0x7
JA afterSwitch
// no support for jump tables
CMPQ DX,$0x7
JE sw7
CMPQ DX,$0x6
JE sw6
CMPQ DX,$0x5
JE sw5
CMPQ DX,$0x4
JE sw4
CMPQ DX,$0x3
JE sw3
CMPQ DX,$0x2
JE sw2
CMPQ DX,$0x1
JE sw1
JMP afterSwitch
sw7: MOVBQZX 6(SI)(DI*1),DX
SHLQ $0x30,DX
ORQ DX,R11
sw6: MOVBQZX 0x5(SI)(DI*1),DX
SHLQ $0x28,DX
ORQ DX,R11
sw5: MOVBQZX 0x4(SI)(DI*1),DX
SHLQ $0x20,DX
ORQ DX,R11
sw4: MOVBQZX 0x3(SI)(DI*1),DX
SHLQ $0x18,DX
ORQ DX,R11
sw3: MOVBQZX 0x2(SI)(DI*1),DX
SHLQ $0x10,DX
ORQ DX,R11
sw2: MOVBQZX 0x1(SI)(DI*1),DX
SHLQ $0x8,DX
ORQ DX,R11
sw1: MOVBQZX 0(SI)(DI*1),DX
ORQ DX,R11
afterSwitch:
LEAQ (AX)(R9*1),SI
XORQ R11,CX
RORQ $0x33,AX
ADDQ CX,BX
MOVQ CX,DX
XORQ SI,AX
RORQ $0x30,DX
RORQ $0x20,SI
LEAQ 0(BX)(AX*1),CX
XORQ BX,DX
RORQ $0x2F,AX
ADDQ DX,SI
RORQ $0x2B,DX
XORQ CX,AX
XORQ SI,DX
RORQ $0x20,CX
ADDQ AX,SI
RORQ $0x33,AX
ADDQ DX,CX
XORQ SI,AX
RORQ $0x30,DX
RORQ $0x20,SI
XORQ CX,DX
ADDQ AX,CX
RORQ $0x2F,AX
ADDQ DX,SI
XORQ CX,AX
RORQ $0x2B,DX
RORQ $0x20,CX
XORQ SI,DX
XORQ R11,SI
XORB $0xEE,CL
ADDQ AX,SI
RORQ $0x33,AX
ADDQ DX,CX
RORQ $0x30,DX
XORQ SI,AX
XORQ CX,DX
RORQ $0x20,SI
ADDQ AX,CX
ADDQ DX,SI
RORQ $0x2F,AX
RORQ $0x2B,DX
XORQ CX,AX
XORQ SI,DX
RORQ $0x20,CX
ADDQ AX,SI
ADDQ DX,CX
RORQ $0x33,AX
RORQ $0x30,DX
XORQ SI,AX
RORQ $0x20,SI
XORQ CX,DX
ADDQ AX,CX
RORQ $0x2F,AX
ADDQ DX,SI
RORQ $0x2B,DX
XORQ CX,AX
XORQ SI,DX
RORQ $0x20,CX
ADDQ AX,SI
ADDQ DX,CX
RORQ $0x33,AX
RORQ $0x30,DX
XORQ CX,DX
XORQ SI,AX
RORQ $0x20,SI
ADDQ DX,SI
ADDQ AX,CX
RORQ $0x2F,AX
XORQ CX,AX
RORQ $0x2B,DX
RORQ $0x20,CX
XORQ SI,DX
// gcc optimized the tail end of this function differently. However,
// we need to preserve out registers to carry out the second stage of
// the finalization. This is a duplicate of an earlier finalization
// round.
ADDQ AX,SI
RORQ $0x33,AX
ADDQ DX,CX
RORQ $0x30,DX
XORQ SI,AX
XORQ CX,DX
RORQ $0x20,SI
ADDQ AX,CX
ADDQ DX,SI
RORQ $0x2F,AX
RORQ $0x2B,DX
XORQ CX,AX
XORQ SI,DX
RORQ $0x20,CX
// Stuff the result into BX instead of AX as gcc had done
MOVQ SI,BX
XORQ AX,BX
XORQ DX,BX
XORQ CX,BX
MOVQ BX,ret+40(FP)
// Start the second finalization round
XORB $0xDD,AL
ADDQ AX,SI
RORQ $0x33,AX
ADDQ DX,CX
RORQ $0x30,DX
XORQ SI,AX
XORQ CX,DX
RORQ $0x20,SI
ADDQ AX,CX
ADDQ DX,SI
RORQ $0x2F,AX
RORQ $0x2B,DX
XORQ CX,AX
XORQ SI,DX
RORQ $0x20,CX
ADDQ AX,SI
ADDQ DX,CX
RORQ $0x33,AX
RORQ $0x30,DX
XORQ SI,AX
RORQ $0x20,SI
XORQ CX,DX
ADDQ AX,CX
RORQ $0x2F,AX
ADDQ DX,SI
RORQ $0x2B,DX
XORQ CX,AX
XORQ SI,DX
RORQ $0x20,CX
ADDQ AX,SI
ADDQ DX,CX
RORQ $0x33,AX
RORQ $0x30,DX
XORQ CX,DX
XORQ SI,AX
RORQ $0x20,SI
ADDQ DX,SI
ADDQ AX,CX
RORQ $0x2F,AX
XORQ CX,AX
RORQ $0x2B,DX
RORQ $0x20,CX
XORQ SI,DX
ADDQ AX,SI
RORQ $0x33,AX
ADDQ DX,CX
RORQ $0x30,DX
XORQ SI,AX
XORQ CX,DX
RORQ $0x20,SI
ADDQ AX,CX
ADDQ DX,SI
RORQ $0x2F,AX
RORQ $0x2B,DX
XORQ CX,AX
XORQ SI,DX
RORQ $0x20,CX
MOVQ SI,BX
XORQ AX,BX
XORQ DX,BX
XORQ CX,BX
MOVQ BX,ret1+48(FP)
RET

View File

@ -1,169 +0,0 @@
#include "textflag.h"
#define R10 g
#define ROUND()\
ADD.S R2,R0,R0;\
ADC R3,R1,R1;\
EOR R2<<13,R0,R8;\
EOR R3>>19,R8,R8;\
EOR R2>>19,R1,R11;\
EOR R3<<13,R11,R11;\
ADD.S R6,R4,R4;\
ADC R7,R5,R5;\
EOR R6<<16,R4,R2;\
EOR R7>>16,R2,R2;\
EOR R6>>16,R5,R3;\
EOR R7<<16,R3,R3;\
ADD.S R2,R1,R1;\
ADC R3,R0,R0;\
EOR R2<<21,R1,R6;\
EOR R3>>11,R6,R6;\
EOR R2>>11,R0,R7;\
EOR R3<<21,R7,R7;\
ADD.S R8,R4,R4;\
ADC R11,R5,R5;\
EOR R8<<17,R4,R2;\
EOR R11>>15,R2,R2;\
EOR R8>>15,R5,R3;\
EOR R11<<17,R3,R3;\
ADD.S R2,R1,R1;\
ADC R3,R0,R0;\
EOR R2<<13,R1,R8;\
EOR R3>>19,R8,R8;\
EOR R2>>19,R0,R11;\
EOR R3<<13,R11,R11;\
ADD.S R6,R5,R5;\
ADC R7,R4,R4;\
EOR R6<<16,R5,R2;\
EOR R7>>16,R2,R2;\
EOR R6>>16,R4,R3;\
EOR R7<<16,R3,R3;\
ADD.S R2,R0,R0;\
ADC R3,R1,R1;\
EOR R2<<21,R0,R6;\
EOR R3>>11,R6,R6;\
EOR R2>>11,R1,R7;\
EOR R3<<21,R7,R7;\
ADD.S R8,R5,R5;\
ADC R11,R4,R4;\
EOR R8<<17,R5,R2;\
EOR R11>>15,R2,R2;\
EOR R8>>15,R4,R3;\
EOR R11<<17,R3,R3;\
// Hash128(k0, k1 uint64, b []byte) (uint64, uint64)
TEXT ·Hash128(SB),NOSPLIT,$8-44
MOVW R10,sav-8(SP)
MOVW k0_lo+0(FP),R12
MOVW k0_hi+4(FP),R14
MOVW $0x70736575,R0
MOVW $0x736f6d65,R1
MOVW $0x6e657261,R4
MOVW $0x6c796765,R5
EOR R12,R0,R0
EOR R14,R1,R1
EOR R12,R4,R4
EOR R14,R5,R5
MOVW k1_lo+8(FP),R12
MOVW k1_hi+12(FP),R14
MOVW $0x6e646f83,R2
MOVW $0x646f7261,R3
MOVW $0x79746573,R6
MOVW $0x74656462,R7
EOR R12,R2,R2
EOR R14,R3,R3
EOR R12,R6,R6
EOR R14,R7,R7
MOVW b+16(FP),R10
MOVW b_len+20(FP),R11
ADD R10,R11,R11
MOVW R11,endb-4(SP)
hashloop128:
MOVW endb-4(SP),R11
SUB R10,R11,R11
SUB.S $8,R11
BLO hashend128
MOVM.IA.W (R10),[R12,R14]
EOR R12,R6,R6
EOR R14,R7,R7
ROUND()
EOR R12,R0,R0
EOR R14,R1,R1
B hashloop128
hashloop128unaligned:
MOVW endb-4(SP),R11
SUB R10,R11,R11
SUB.S $8,R11
BLO hashend128
MOVB (R10),R12
MOVB 1(R10),R11
ORR R11<<8,R12,R12
MOVB 2(R10),R11
ORR R11<<16,R12,R12
MOVB 3(R10),R11
ORR R11<<24,R12,R12
MOVB 4(R10),R14
MOVB 5(R10),R11
ORR R11<<8,R14,R14
MOVB 6(R10),R11
ORR R11<<16,R14,R14
MOVB 7(R10),R11
ORR R11<<24,R14,R14
ADD $8,R10,R10
EOR R12,R6,R6
EOR R14,R7,R7
ROUND()
EOR R12,R0,R0
EOR R14,R1,R1
B hashloop128unaligned
hashend128:
MOVW $0x0,R12
MOVW $0x0,R14
RSB $0,R11,R11
AND.S $7,R11
BEQ hashlast128
MOVW (R10),R12
SLL $3,R11
AND $63,R11
SUB.S $32,R11,R11
BEQ hashlast128
BLO hashhi128
MOVW R12<<R11,R12
MOVW R12>>R11,R12
B hashlast128
hashhi128:
ADD $32,R11
MOVW 4(R10),R14
MOVW R14<<R11,R14
MOVW R14>>R11,R14
hashlast128:
MOVW b_len+20(FP),R11
ORR R11<<24,R14,R14
EOR R12,R6,R6
EOR R14,R7,R7
ROUND()
EOR R12,R0,R0
EOR R14,R1,R1
EOR $238,R4
ROUND()
ROUND()
EOR R0,R2,R12
EOR R1,R3,R14
EOR R4,R12,R12
EOR R5,R14,R14
EOR R6,R12,R12
EOR R7,R14,R14
MOVW R12,ret_lo+28(FP)
MOVW R14,ret_hi+32(FP)
EOR $221,R2
ROUND()
ROUND()
EOR R0,R2,R12
EOR R1,R3,R14
EOR R4,R12,R12
EOR R5,R14,R14
EOR R6,R12,R12
EOR R7,R14,R14
MOVW R12,unnamed_lo+36(FP)
MOVW R14,unnamed_hi+40(FP)
MOVW sav-8(SP),R10
RET

View File

@ -1,201 +0,0 @@
// +build amd64,!appengine,!gccgo
// This is a translation of the gcc output of FloodyBerry's pure-C public
// domain siphash implementation at https://github.com/floodyberry/siphash
// func Hash(k0, k1 uint64, b []byte) uint64
TEXT ·Hash(SB),4,$0-48
MOVQ k0+0(FP),CX
MOVQ $0x736F6D6570736575,R9
MOVQ k1+8(FP),DI
MOVQ $0x6C7967656E657261,BX
MOVQ $0x646F72616E646F6D,AX
MOVQ b_len+24(FP),DX
MOVQ DX,R11
MOVQ DX,R10
XORQ CX,R9
XORQ CX,BX
MOVQ $0x7465646279746573,CX
XORQ DI,AX
XORQ DI,CX
SHLQ $0x38,R11
XORQ DI,DI
MOVQ b_base+16(FP),SI
ANDQ $0xFFFFFFFFFFFFFFF8,R10
JE afterLoop
XCHGQ AX,AX
loopBody:
MOVQ 0(SI)(DI*1),R8
ADDQ AX,R9
RORQ $0x33,AX
XORQ R9,AX
RORQ $0x20,R9
ADDQ $0x8,DI
XORQ R8,CX
ADDQ CX,BX
RORQ $0x30,CX
XORQ BX,CX
ADDQ AX,BX
RORQ $0x2F,AX
ADDQ CX,R9
RORQ $0x2B,CX
XORQ BX,AX
XORQ R9,CX
RORQ $0x20,BX
ADDQ AX,R9
ADDQ CX,BX
RORQ $0x33,AX
RORQ $0x30,CX
XORQ R9,AX
XORQ BX,CX
RORQ $0x20,R9
ADDQ AX,BX
ADDQ CX,R9
RORQ $0x2F,AX
RORQ $0x2B,CX
XORQ BX,AX
RORQ $0x20,BX
XORQ R9,CX
XORQ R8,R9
CMPQ R10,DI
JA loopBody
afterLoop:
SUBQ R10,DX
CMPQ DX,$0x7
JA afterSwitch
// no support for jump tables
CMPQ DX,$0x7
JE sw7
CMPQ DX,$0x6
JE sw6
CMPQ DX,$0x5
JE sw5
CMPQ DX,$0x4
JE sw4
CMPQ DX,$0x3
JE sw3
CMPQ DX,$0x2
JE sw2
CMPQ DX,$0x1
JE sw1
JMP afterSwitch
sw7: MOVBQZX 6(SI)(DI*1),DX
SHLQ $0x30,DX
ORQ DX,R11
sw6: MOVBQZX 0x5(SI)(DI*1),DX
SHLQ $0x28,DX
ORQ DX,R11
sw5: MOVBQZX 0x4(SI)(DI*1),DX
SHLQ $0x20,DX
ORQ DX,R11
sw4: MOVBQZX 0x3(SI)(DI*1),DX
SHLQ $0x18,DX
ORQ DX,R11
sw3: MOVBQZX 0x2(SI)(DI*1),DX
SHLQ $0x10,DX
ORQ DX,R11
sw2: MOVBQZX 0x1(SI)(DI*1),DX
SHLQ $0x8,DX
ORQ DX,R11
sw1: MOVBQZX 0(SI)(DI*1),DX
ORQ DX,R11
afterSwitch:
LEAQ (AX)(R9*1),SI
XORQ R11,CX
RORQ $0x33,AX
ADDQ CX,BX
MOVQ CX,DX
XORQ SI,AX
RORQ $0x30,DX
RORQ $0x20,SI
LEAQ 0(BX)(AX*1),CX
XORQ BX,DX
RORQ $0x2F,AX
ADDQ DX,SI
RORQ $0x2B,DX
XORQ CX,AX
XORQ SI,DX
RORQ $0x20,CX
ADDQ AX,SI
RORQ $0x33,AX
ADDQ DX,CX
XORQ SI,AX
RORQ $0x30,DX
RORQ $0x20,SI
XORQ CX,DX
ADDQ AX,CX
RORQ $0x2F,AX
ADDQ DX,SI
XORQ CX,AX
RORQ $0x2B,DX
RORQ $0x20,CX
XORQ SI,DX
XORQ R11,SI
XORB $0xFF,CL
ADDQ AX,SI
RORQ $0x33,AX
ADDQ DX,CX
RORQ $0x30,DX
XORQ SI,AX
XORQ CX,DX
RORQ $0x20,SI
ADDQ AX,CX
ADDQ DX,SI
RORQ $0x2F,AX
RORQ $0x2B,DX
XORQ CX,AX
XORQ SI,DX
RORQ $0x20,CX
ADDQ AX,SI
ADDQ DX,CX
RORQ $0x33,AX
RORQ $0x30,DX
XORQ SI,AX
RORQ $0x20,SI
XORQ CX,DX
ADDQ AX,CX
RORQ $0x2F,AX
ADDQ DX,SI
RORQ $0x2B,DX
XORQ CX,AX
XORQ SI,DX
RORQ $0x20,CX
ADDQ AX,SI
ADDQ DX,CX
RORQ $0x33,AX
RORQ $0x30,DX
XORQ CX,DX
XORQ SI,AX
RORQ $0x20,SI
ADDQ DX,SI
ADDQ AX,CX
RORQ $0x2F,AX
XORQ CX,AX
RORQ $0x2B,DX
RORQ $0x20,CX
XORQ SI,DX
ADDQ AX,SI
RORQ $0x33,AX
ADDQ DX,CX
XORQ SI,AX
RORQ $0x30,DX
XORQ CX,DX
ADDQ AX,CX
RORQ $0x2F,AX
XORQ CX,AX
RORQ $0x2B,DX
RORQ $0x20,CX
XORQ DX,AX
XORQ CX,AX
MOVQ AX,ret+40(FP)
RET

View File

@ -1,160 +0,0 @@
#include "textflag.h"
#define R10 g
#define ROUND()\
ADD.S R2,R0,R0;\
ADC R3,R1,R1;\
EOR R2<<13,R0,R8;\
EOR R3>>19,R8,R8;\
EOR R2>>19,R1,R11;\
EOR R3<<13,R11,R11;\
ADD.S R6,R4,R4;\
ADC R7,R5,R5;\
EOR R6<<16,R4,R2;\
EOR R7>>16,R2,R2;\
EOR R6>>16,R5,R3;\
EOR R7<<16,R3,R3;\
ADD.S R2,R1,R1;\
ADC R3,R0,R0;\
EOR R2<<21,R1,R6;\
EOR R3>>11,R6,R6;\
EOR R2>>11,R0,R7;\
EOR R3<<21,R7,R7;\
ADD.S R8,R4,R4;\
ADC R11,R5,R5;\
EOR R8<<17,R4,R2;\
EOR R11>>15,R2,R2;\
EOR R8>>15,R5,R3;\
EOR R11<<17,R3,R3;\
ADD.S R2,R1,R1;\
ADC R3,R0,R0;\
EOR R2<<13,R1,R8;\
EOR R3>>19,R8,R8;\
EOR R2>>19,R0,R11;\
EOR R3<<13,R11,R11;\
ADD.S R6,R5,R5;\
ADC R7,R4,R4;\
EOR R6<<16,R5,R2;\
EOR R7>>16,R2,R2;\
EOR R6>>16,R4,R3;\
EOR R7<<16,R3,R3;\
ADD.S R2,R0,R0;\
ADC R3,R1,R1;\
EOR R2<<21,R0,R6;\
EOR R3>>11,R6,R6;\
EOR R2>>11,R1,R7;\
EOR R3<<21,R7,R7;\
ADD.S R8,R5,R5;\
ADC R11,R4,R4;\
EOR R8<<17,R5,R2;\
EOR R11>>15,R2,R2;\
EOR R8>>15,R4,R3;\
EOR R11<<17,R3,R3;\
// Hash(k0, k1 uint64, b []byte) uint64
TEXT ·Hash(SB),NOSPLIT,$8-36
MOVW R10,sav-8(SP)
MOVW k0_lo+0(FP),R12
MOVW k0_hi+4(FP),R14
MOVW $0x70736575,R0
MOVW $0x736f6d65,R1
MOVW $0x6e657261,R4
MOVW $0x6c796765,R5
EOR R12,R0,R0
EOR R14,R1,R1
EOR R12,R4,R4
EOR R14,R5,R5
MOVW k1_lo+8(FP),R12
MOVW k1_hi+12(FP),R14
MOVW $0x6e646f6d,R2
MOVW $0x646f7261,R3
MOVW $0x79746573,R6
MOVW $0x74656462,R7
EOR R12,R2,R2
EOR R14,R3,R3
EOR R12,R6,R6
EOR R14,R7,R7
MOVW b+16(FP),R10
MOVW b_len+20(FP),R11
ADD R10,R11,R11
MOVW R11,endb-4(SP)
AND.S $3,R10,R8
BNE hashloopunaligned
hashloop:
MOVW endb-4(SP),R11
SUB R10,R11,R11
SUB.S $8,R11
BLO hashend
MOVM.IA.W (R10),[R12,R14]
EOR R12,R6,R6
EOR R14,R7,R7
ROUND()
EOR R12,R0,R0
EOR R14,R1,R1
B hashloop
hashloopunaligned:
MOVW endb-4(SP),R11
SUB R10,R11,R11
SUB.S $8,R11
BLO hashend
MOVB (R10),R12
MOVB 1(R10),R11
ORR R11<<8,R12,R12
MOVB 2(R10),R11
ORR R11<<16,R12,R12
MOVB 3(R10),R11
ORR R11<<24,R12,R12
MOVB 4(R10),R14
MOVB 5(R10),R11
ORR R11<<8,R14,R14
MOVB 6(R10),R11
ORR R11<<16,R14,R14
MOVB 7(R10),R11
ORR R11<<24,R14,R14
ADD $8,R10,R10
EOR R12,R6,R6
EOR R14,R7,R7
ROUND()
EOR R12,R0,R0
EOR R14,R1,R1
B hashloopunaligned
hashend:
MOVW $0x0,R12
MOVW $0x0,R14
RSB $0,R11,R11
AND.S $7,R11
BEQ hashlast
MOVW (R10),R12
SLL $3,R11
AND $63,R11
SUB.S $32,R11,R11
BEQ hashlast
BLO hashhi
MOVW R12<<R11,R12
MOVW R12>>R11,R12
B hashlast
hashhi:
ADD $32,R11
MOVW 4(R10),R14
MOVW R14<<R11,R14
MOVW R14>>R11,R14
hashlast:
MOVW b_len+20(FP),R11
ORR R11<<24,R14,R14
EOR R12,R6,R6
EOR R14,R7,R7
ROUND()
EOR R12,R0,R0
EOR R14,R1,R1
EOR $255,R4
ROUND()
ROUND()
EOR R2,R0,R0
EOR R3,R1,R1
EOR R6,R4,R4
EOR R7,R5,R5
EOR R4,R0,R0
EOR R5,R1,R1
MOVW sav-8(SP),R10
MOVW R0,ret_lo+28(FP)
MOVW R1,ret_hi+32(FP)
RET

View File

@ -1,33 +0,0 @@
// +build arm amd64,!appengine,!gccgo
// Written in 2012 by Dmitry Chestnykh.
//
// To the extent possible under law, the author have dedicated all copyright
// and related and neighboring rights to this software to the public domain
// worldwide. This software is distributed without any warranty.
// http://creativecommons.org/publicdomain/zero/1.0/
// This file contains a function definition for use with assembly implementations of Hash()
package siphash
//go:noescape
// Hash returns the 64-bit SipHash-2-4 of the given byte slice with two 64-bit
// parts of 128-bit key: k0 and k1.
func Hash(k0, k1 uint64, b []byte) uint64
//go:noescape
// Hash128 returns the 128-bit SipHash-2-4 of the given byte slice with two
// 64-bit parts of 128-bit key: k0 and k1.
func Hash128(k0, k1 uint64, b []byte) (uint64, uint64)
//go:noescape
func blocks(d *digest, p []uint8)
//go:noescape
func finalize(d *digest) uint64
//go:noescape
func once(d *digest)

View File

@ -1,318 +0,0 @@
// Written in 2012-2014 by Dmitry Chestnykh.
//
// To the extent possible under law, the author have dedicated all copyright
// and related and neighboring rights to this software to the public domain
// worldwide. This software is distributed without any warranty.
// http://creativecommons.org/publicdomain/zero/1.0/
// Package siphash implements SipHash-2-4, a fast short-input PRF
// created by Jean-Philippe Aumasson and Daniel J. Bernstein.
package siphash
import "hash"
const (
// BlockSize is the block size of hash algorithm in bytes.
BlockSize = 8
// Size is the size of hash output in bytes.
Size = 8
// Size128 is the size of 128-bit hash output in bytes.
Size128 = 16
)
type digest struct {
v0, v1, v2, v3 uint64 // state
k0, k1 uint64 // two parts of key
x [8]byte // buffer for unprocessed bytes
nx int // number of bytes in buffer x
size int // output size in bytes (8 or 16)
t uint8 // message bytes counter (mod 256)
}
// newDigest returns a new digest with the given output size in bytes (must be 8 or 16).
func newDigest(size int, key []byte) *digest {
if size != Size && size != Size128 {
panic("size must be 8 or 16")
}
d := new(digest)
d.k0 = uint64(key[0]) | uint64(key[1])<<8 | uint64(key[2])<<16 | uint64(key[3])<<24 |
uint64(key[4])<<32 | uint64(key[5])<<40 | uint64(key[6])<<48 | uint64(key[7])<<56
d.k1 = uint64(key[8]) | uint64(key[9])<<8 | uint64(key[10])<<16 | uint64(key[11])<<24 |
uint64(key[12])<<32 | uint64(key[13])<<40 | uint64(key[14])<<48 | uint64(key[15])<<56
d.size = size
d.Reset()
return d
}
// New returns a new hash.Hash64 computing SipHash-2-4 with 16-byte key and 8-byte output.
func New(key []byte) hash.Hash64 {
return newDigest(Size, key)
}
// New128 returns a new hash.Hash computing SipHash-2-4 with 16-byte key and 16-byte output.
//
// Note that 16-byte output is considered experimental by SipHash authors at this time.
func New128(key []byte) hash.Hash {
return newDigest(Size128, key)
}
func (d *digest) Reset() {
d.v0 = d.k0 ^ 0x736f6d6570736575
d.v1 = d.k1 ^ 0x646f72616e646f6d
d.v2 = d.k0 ^ 0x6c7967656e657261
d.v3 = d.k1 ^ 0x7465646279746573
d.t = 0
d.nx = 0
if d.size == Size128 {
d.v1 ^= 0xee
}
}
func (d *digest) Size() int { return d.size }
func (d *digest) BlockSize() int { return BlockSize }
func (d *digest) Write(p []byte) (nn int, err error) {
nn = len(p)
d.t += uint8(nn)
if d.nx > 0 {
n := len(p)
if n > BlockSize-d.nx {
n = BlockSize - d.nx
}
d.nx += copy(d.x[d.nx:], p)
if d.nx == BlockSize {
once(d)
d.nx = 0
}
p = p[n:]
}
if len(p) >= BlockSize {
n := len(p) &^ (BlockSize - 1)
blocks(d, p[:n])
p = p[n:]
}
if len(p) > 0 {
d.nx = copy(d.x[:], p)
}
return
}
func (d *digest) Sum64() uint64 {
for i := d.nx; i < BlockSize-1; i++ {
d.x[i] = 0
}
d.x[7] = d.t
return finalize(d)
}
func (d0 *digest) sum128() (r0, r1 uint64) {
// Make a copy of d0 so that caller can keep writing and summing.
d := *d0
for i := d.nx; i < BlockSize-1; i++ {
d.x[i] = 0
}
d.x[7] = d.t
blocks(&d, d.x[:])
v0, v1, v2, v3 := d.v0, d.v1, d.v2, d.v3
v2 ^= 0xee
// Round 1.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 2.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 3.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 4.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
r0 = v0 ^ v1 ^ v2 ^ v3
v1 ^= 0xdd
// Round 1.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 2.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 3.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 4.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
r1 = v0 ^ v1 ^ v2 ^ v3
return r0, r1
}
func (d *digest) Sum(in []byte) []byte {
if d.size == Size {
r := d.Sum64()
in = append(in,
byte(r),
byte(r>>8),
byte(r>>16),
byte(r>>24),
byte(r>>32),
byte(r>>40),
byte(r>>48),
byte(r>>56))
} else {
r0, r1 := d.sum128()
in = append(in,
byte(r0),
byte(r0>>8),
byte(r0>>16),
byte(r0>>24),
byte(r0>>32),
byte(r0>>40),
byte(r0>>48),
byte(r0>>56),
byte(r1),
byte(r1>>8),
byte(r1>>16),
byte(r1>>24),
byte(r1>>32),
byte(r1>>40),
byte(r1>>48),
byte(r1>>56))
}
return in
}

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2017 ginuerzh
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,2 +0,0 @@
# gosocks4
golang and SOCKS4(a)

View File

@ -1,252 +0,0 @@
// SOCKS Protocol Version 4(a)
// https://www.openssh.com/txt/socks4.protocol
// https://www.openssh.com/txt/socks4a.protocol
package gosocks4
import (
"bufio"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"strconv"
)
const (
Ver4 = 4
)
const (
CmdConnect uint8 = 1
CmdBind = 2
)
const (
AddrIPv4 = 0
AddrDomain = 1
)
const (
Granted = 90
Failed = 91
Rejected = 92
RejectedUserid = 93
)
var (
ErrBadVersion = errors.New("Bad version")
ErrBadFormat = errors.New("Bad format")
ErrBadAddrType = errors.New("Bad address type")
ErrShortDataLength = errors.New("Short data length")
ErrBadCmd = errors.New("Bad Command")
)
type Addr struct {
Type int
Host string
Port uint16
}
func (addr *Addr) Decode(b []byte) error {
if len(b) < 6 {
return ErrShortDataLength
}
addr.Port = binary.BigEndian.Uint16(b[0:2])
addr.Host = net.IP(b[2 : 2+net.IPv4len]).String()
if b[2]|b[3]|b[4] == 0 && b[5] != 0 {
addr.Type = AddrDomain
}
return nil
}
func (addr *Addr) Encode(b []byte) error {
if len(b) < 6 {
return ErrShortDataLength
}
binary.BigEndian.PutUint16(b[0:2], addr.Port)
switch addr.Type {
case AddrIPv4:
ip4 := net.ParseIP(addr.Host).To4()
if ip4 == nil {
return ErrBadAddrType
}
copy(b[2:], ip4)
case AddrDomain:
ip4 := net.IPv4(0, 0, 0, 1)
copy(b[2:], ip4.To4())
default:
return ErrBadAddrType
}
return nil
}
func (addr *Addr) String() string {
return net.JoinHostPort(addr.Host, strconv.Itoa(int(addr.Port)))
}
/*
+----+----+----+----+----+----+----+----+----+----+....+----+
| VN | CD | DSTPORT | DSTIP | USERID |NULL|
+----+----+----+----+----+----+----+----+----+----+....+----+
1 1 2 4 variable 1
*/
type Request struct {
Cmd uint8
Addr *Addr
Userid []byte
}
func NewRequest(cmd uint8, addr *Addr, userid []byte) *Request {
return &Request{
Cmd: cmd,
Addr: addr,
Userid: userid,
}
}
func ReadRequest(r io.Reader) (*Request, error) {
br := bufio.NewReader(r)
b, err := br.Peek(8)
if err != nil {
return nil, err
}
if b[0] != Ver4 {
return nil, ErrBadVersion
}
request := &Request{
Cmd: b[1],
}
addr := &Addr{}
if err := addr.Decode(b[2:8]); err != nil {
return nil, err
}
request.Addr = addr
if _, err := br.Discard(8); err != nil {
return nil, err
}
b, err = br.ReadBytes(0)
if err != nil {
return nil, err
}
request.Userid = b[:len(b)-1]
if request.Addr.Type == AddrDomain {
b, err = br.ReadBytes(0)
if err != nil {
return nil, err
}
request.Addr.Host = string(b[:len(b)-1])
}
return request, nil
}
func (r *Request) Write(w io.Writer) (err error) {
bw := bufio.NewWriter(w)
bw.Write([]byte{Ver4, r.Cmd})
if r.Addr == nil {
return ErrBadAddrType
}
var b [6]byte
if err = r.Addr.Encode(b[:]); err != nil {
return
}
bw.Write(b[:])
if len(r.Userid) > 0 {
bw.Write(r.Userid)
}
bw.WriteByte(0)
if r.Addr.Type == AddrDomain {
bw.WriteString(r.Addr.Host)
bw.WriteByte(0)
}
return bw.Flush()
}
func (r *Request) String() string {
addr := r.Addr
if addr == nil {
addr = &Addr{}
}
return fmt.Sprintf("%d %d %s", Ver4, r.Cmd, addr.String())
}
/*
+----+----+----+----+----+----+----+----+
| VN | CD | DSTPORT | DSTIP |
+----+----+----+----+----+----+----+----+
1 1 2 4
*/
type Reply struct {
Code uint8
Addr *Addr
}
func NewReply(code uint8, addr *Addr) *Reply {
return &Reply{
Code: code,
Addr: addr,
}
}
func ReadReply(r io.Reader) (*Reply, error) {
var b [8]byte
_, err := io.ReadFull(r, b[:])
if err != nil {
return nil, err
}
if b[0] != 0 {
return nil, ErrBadVersion
}
reply := &Reply{
Code: b[1],
}
reply.Addr = &Addr{}
if err := reply.Addr.Decode(b[2:]); err != nil {
return nil, err
}
return reply, nil
}
func (r *Reply) Write(w io.Writer) (err error) {
var b [8]byte
b[1] = r.Code
if r.Addr != nil {
if err = r.Addr.Encode(b[2:]); err != nil {
return
}
}
_, err = w.Write(b[:])
return
}
func (r *Reply) String() string {
addr := r.Addr
if addr == nil {
addr = &Addr{}
}
return fmt.Sprintf("0 %d %s", r.Code, addr.String())
}

View File

@ -1,152 +0,0 @@
SOCKS: A protocol for TCP proxy across firewalls
Ying-Da Lee
Principal Member Technical Staff
NEC Systems Laboratory, CSTC
ylee@syl.dl.nec.com
SOCKS was originally developed by David Koblas and subsequently modified
and extended by me to its current running version -- version 4. It is a
protocol that relays TCP sessions at a firewall host to allow application
users transparent access across the firewall. Because the protocol is
independent of application protocols, it can be (and has been) used for
many different services, such as telnet, ftp, finger, whois, gopher, WWW,
etc. Access control can be applied at the beginning of each TCP session;
thereafter the server simply relays the data between the client and the
application server, incurring minimum processing overhead. Since SOCKS
never has to know anything about the application protocol, it should also
be easy for it to accommodate applications which use encryption to protect
their traffic from nosey snoopers.
Two operations are defined: CONNECT and BIND.
1) CONNECT
The client connects to the SOCKS server and sends a CONNECT request when
it wants to establish a connection to an application server. The client
includes in the request packet the IP address and the port number of the
destination host, and userid, in the following format.
+----+----+----+----+----+----+----+----+----+----+....+----+
| VN | CD | DSTPORT | DSTIP | USERID |NULL|
+----+----+----+----+----+----+----+----+----+----+....+----+
# of bytes: 1 1 2 4 variable 1
VN is the SOCKS protocol version number and should be 4. CD is the
SOCKS command code and should be 1 for CONNECT request. NULL is a byte
of all zero bits.
The SOCKS server checks to see whether such a request should be granted
based on any combination of source IP address, destination IP address,
destination port number, the userid, and information it may obtain by
consulting IDENT, cf. RFC 1413. If the request is granted, the SOCKS
server makes a connection to the specified port of the destination host.
A reply packet is sent to the client when this connection is established,
or when the request is rejected or the operation fails.
+----+----+----+----+----+----+----+----+
| VN | CD | DSTPORT | DSTIP |
+----+----+----+----+----+----+----+----+
# of bytes: 1 1 2 4
VN is the version of the reply code and should be 0. CD is the result
code with one of the following values:
90: request granted
91: request rejected or failed
92: request rejected becasue SOCKS server cannot connect to
identd on the client
93: request rejected because the client program and identd
report different user-ids
The remaining fields are ignored.
The SOCKS server closes its connection immediately after notifying
the client of a failed or rejected request. For a successful request,
the SOCKS server gets ready to relay traffic on both directions. This
enables the client to do I/O on its connection as if it were directly
connected to the application server.
2) BIND
The client connects to the SOCKS server and sends a BIND request when
it wants to prepare for an inbound connection from an application server.
This should only happen after a primary connection to the application
server has been established with a CONNECT. Typically, this is part of
the sequence of actions:
-bind(): obtain a socket
-getsockname(): get the IP address and port number of the socket
-listen(): ready to accept call from the application server
-use the primary connection to inform the application server of
the IP address and the port number that it should connect to.
-accept(): accept a connection from the application server
The purpose of SOCKS BIND operation is to support such a sequence
but using a socket on the SOCKS server rather than on the client.
The client includes in the request packet the IP address of the
application server, the destination port used in the primary connection,
and the userid.
+----+----+----+----+----+----+----+----+----+----+....+----+
| VN | CD | DSTPORT | DSTIP | USERID |NULL|
+----+----+----+----+----+----+----+----+----+----+....+----+
# of bytes: 1 1 2 4 variable 1
VN is again 4 for the SOCKS protocol version number. CD must be 2 to
indicate BIND request.
The SOCKS server uses the client information to decide whether the
request is to be granted. The reply it sends back to the client has
the same format as the reply for CONNECT request, i.e.,
+----+----+----+----+----+----+----+----+
| VN | CD | DSTPORT | DSTIP |
+----+----+----+----+----+----+----+----+
# of bytes: 1 1 2 4
VN is the version of the reply code and should be 0. CD is the result
code with one of the following values:
90: request granted
91: request rejected or failed
92: request rejected becasue SOCKS server cannot connect to
identd on the client
93: request rejected because the client program and identd
report different user-ids.
However, for a granted request (CD is 90), the DSTPORT and DSTIP fields
are meaningful. In that case, the SOCKS server obtains a socket to wait
for an incoming connection and sends the port number and the IP address
of that socket to the client in DSTPORT and DSTIP, respectively. If the
DSTIP in the reply is 0 (the value of constant INADDR_ANY), then the
client should replace it by the IP address of the SOCKS server to which
the cleint is connected. (This happens if the SOCKS server is not a
multi-homed host.) In the typical scenario, these two numbers are
made available to the application client prgram via the result of the
subsequent getsockname() call. The application protocol must provide a
way for these two pieces of information to be sent from the client to
the application server so that it can initiate the connection, which
connects it to the SOCKS server rather than directly to the application
client as it normally would.
The SOCKS server sends a second reply packet to the client when the
anticipated connection from the application server is established.
The SOCKS server checks the IP address of the originating host against
the value of DSTIP specified in the client's BIND request. If a mismatch
is found, the CD field in the second reply is set to 91 and the SOCKS
server closes both connections. If the two match, CD in the second
reply is set to 90 and the SOCKS server gets ready to relay the traffic
on its two connections. From then on the client does I/O on its connection
to the SOCKS server as if it were directly connected to the application
server.
For both CONNECT and BIND operations, the server sets a time limit
(2 minutes in current CSTC implementation) for the establishment of its
connection with the application server. If the connection is still not
establiched when the time limit expires, the server closes its connection
to the client and gives up.

View File

@ -1,39 +0,0 @@
SOCKS 4A: A Simple Extension to SOCKS 4 Protocol
Ying-Da Lee
yingda@best.com or yingda@esd.sgi.com
Please read SOCKS4.protocol first for an description of the version 4
protocol. This extension is intended to allow the use of SOCKS on hosts
which are not capable of resolving all domain names.
In version 4, the client sends the following packet to the SOCKS server
to request a CONNECT or a BIND operation:
+----+----+----+----+----+----+----+----+----+----+....+----+
| VN | CD | DSTPORT | DSTIP | USERID |NULL|
+----+----+----+----+----+----+----+----+----+----+....+----+
# of bytes: 1 1 2 4 variable 1
VN is the SOCKS protocol version number and should be 4. CD is the
SOCKS command code and should be 1 for CONNECT or 2 for BIND. NULL
is a byte of all zero bits.
For version 4A, if the client cannot resolve the destination host's
domain name to find its IP address, it should set the first three bytes
of DSTIP to NULL and the last byte to a non-zero value. (This corresponds
to IP address 0.0.0.x, with x nonzero. As decreed by IANA -- The
Internet Assigned Numbers Authority -- such an address is inadmissible
as a destination IP address and thus should never occur if the client
can resolve the domain name.) Following the NULL byte terminating
USERID, the client must sends the destination domain name and termiantes
it with another NULL byte. This is used for both CONNECT and BIND requests.
A server using protocol 4A must check the DSTIP in the request packet.
If it represent address 0.0.0.x with nonzero x, the server must read
in the domain name that the client sends in the packet. The server
should resolve the domain name and make connection to the destination
host if it can.
SOCKSified sockd may pass domain names that it cannot resolve to
the next-hop SOCKS server.

View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2014 郑锐
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,4 +0,0 @@
gosocks5
========
golang and SOCKSV5

View File

@ -1,171 +0,0 @@
package gosocks5
import (
"io"
//"log"
"net"
"sync"
"time"
)
type Selector interface {
// return supported methods
Methods() []uint8
// select method
Select(methods ...uint8) (method uint8)
// on method selected
OnSelected(method uint8, conn net.Conn) (net.Conn, error)
}
type Conn struct {
c net.Conn
selector Selector
method uint8
isClient bool
handshaked bool
handshakeMutex sync.Mutex
handshakeErr error
}
func ClientConn(conn net.Conn, selector Selector) *Conn {
return &Conn{
c: conn,
selector: selector,
isClient: true,
}
}
func ServerConn(conn net.Conn, selector Selector) *Conn {
return &Conn{
c: conn,
selector: selector,
}
}
func (conn *Conn) Handleshake() error {
conn.handshakeMutex.Lock()
defer conn.handshakeMutex.Unlock()
if err := conn.handshakeErr; err != nil {
return err
}
if conn.handshaked {
return nil
}
if conn.isClient {
conn.handshakeErr = conn.clientHandshake()
} else {
conn.handshakeErr = conn.serverHandshake()
}
return conn.handshakeErr
}
func (conn *Conn) clientHandshake() error {
var methods []uint8
var nm int
if conn.selector != nil {
methods = conn.selector.Methods()
}
nm = len(methods)
if nm == 0 {
nm = 1
}
b := make([]byte, 2+nm)
b[0] = Ver5
b[1] = uint8(nm)
copy(b[2:], methods)
if _, err := conn.c.Write(b); err != nil {
return err
}
if _, err := io.ReadFull(conn.c, b[:2]); err != nil {
return err
}
if b[0] != Ver5 {
return ErrBadVersion
}
if conn.selector != nil {
c, err := conn.selector.OnSelected(b[1], conn.c)
if err != nil {
return err
}
conn.c = c
}
conn.method = b[1]
//log.Println("method:", conn.method)
conn.handshaked = true
return nil
}
func (conn *Conn) serverHandshake() error {
methods, err := ReadMethods(conn.c)
if err != nil {
return err
}
method := MethodNoAuth
if conn.selector != nil {
method = conn.selector.Select(methods...)
}
if _, err := conn.c.Write([]byte{Ver5, method}); err != nil {
return err
}
if conn.selector != nil {
c, err := conn.selector.OnSelected(method, conn.c)
if err != nil {
return err
}
conn.c = c
}
conn.method = method
//log.Println("method:", method)
conn.handshaked = true
return nil
}
func (conn *Conn) Read(b []byte) (n int, err error) {
if err = conn.Handleshake(); err != nil {
return
}
return conn.c.Read(b)
}
func (conn *Conn) Write(b []byte) (n int, err error) {
if err = conn.Handleshake(); err != nil {
return
}
return conn.c.Write(b)
}
func (conn *Conn) Close() error {
return conn.c.Close()
}
func (conn *Conn) LocalAddr() net.Addr {
return conn.c.LocalAddr()
}
func (conn *Conn) RemoteAddr() net.Addr {
return conn.c.RemoteAddr()
}
func (conn *Conn) SetDeadline(t time.Time) error {
return conn.c.SetDeadline(t)
}
func (conn *Conn) SetReadDeadline(t time.Time) error {
return conn.c.SetReadDeadline(t)
}
func (conn *Conn) SetWriteDeadline(t time.Time) error {
return conn.c.SetWriteDeadline(t)
}

View File

@ -1,507 +0,0 @@
Network Working Group M. Leech
Request for Comments: 1928 Bell-Northern Research Ltd
Category: Standards Track M. Ganis
International Business Machines
Y. Lee
NEC Systems Laboratory
R. Kuris
Unify Corporation
D. Koblas
Independent Consultant
L. Jones
Hewlett-Packard Company
March 1996
SOCKS Protocol Version 5
Status of this Memo
This document specifies an Internet standards track protocol for the
Internet community, and requests discussion and suggestions for
improvements. Please refer to the current edition of the "Internet
Official Protocol Standards" (STD 1) for the standardization state
and status of this protocol. Distribution of this memo is unlimited.
Acknowledgments
This memo describes a protocol that is an evolution of the previous
version of the protocol, version 4 [1]. This new protocol stems from
active discussions and prototype implementations. The key
contributors are: Marcus Leech: Bell-Northern Research, David Koblas:
Independent Consultant, Ying-Da Lee: NEC Systems Laboratory, LaMont
Jones: Hewlett-Packard Company, Ron Kuris: Unify Corporation, Matt
Ganis: International Business Machines.
1. Introduction
The use of network firewalls, systems that effectively isolate an
organizations internal network structure from an exterior network,
such as the INTERNET is becoming increasingly popular. These
firewall systems typically act as application-layer gateways between
networks, usually offering controlled TELNET, FTP, and SMTP access.
With the emergence of more sophisticated application layer protocols
designed to facilitate global information discovery, there exists a
need to provide a general framework for these protocols to
transparently and securely traverse a firewall.
Leech, et al Standards Track [Page 1]
RFC 1928 SOCKS Protocol Version 5 March 1996
There exists, also, a need for strong authentication of such
traversal in as fine-grained a manner as is practical. This
requirement stems from the realization that client-server
relationships emerge between the networks of various organizations,
and that such relationships need to be controlled and often strongly
authenticated.
The protocol described here is designed to provide a framework for
client-server applications in both the TCP and UDP domains to
conveniently and securely use the services of a network firewall.
The protocol is conceptually a "shim-layer" between the application
layer and the transport layer, and as such does not provide network-
layer gateway services, such as forwarding of ICMP messages.
2. Existing practice
There currently exists a protocol, SOCKS Version 4, that provides for
unsecured firewall traversal for TCP-based client-server
applications, including TELNET, FTP and the popular information-
discovery protocols such as HTTP, WAIS and GOPHER.
This new protocol extends the SOCKS Version 4 model to include UDP,
and extends the framework to include provisions for generalized
strong authentication schemes, and extends the addressing scheme to
encompass domain-name and V6 IP addresses.
The implementation of the SOCKS protocol typically involves the
recompilation or relinking of TCP-based client applications to use
the appropriate encapsulation routines in the SOCKS library.
Note:
Unless otherwise noted, the decimal numbers appearing in packet-
format diagrams represent the length of the corresponding field, in
octets. Where a given octet must take on a specific value, the
syntax X'hh' is used to denote the value of the single octet in that
field. When the word 'Variable' is used, it indicates that the
corresponding field has a variable length defined either by an
associated (one or two octet) length field, or by a data type field.
3. Procedure for TCP-based clients
When a TCP-based client wishes to establish a connection to an object
that is reachable only via a firewall (such determination is left up
to the implementation), it must open a TCP connection to the
appropriate SOCKS port on the SOCKS server system. The SOCKS service
is conventionally located on TCP port 1080. If the connection
request succeeds, the client enters a negotiation for the
Leech, et al Standards Track [Page 2]
RFC 1928 SOCKS Protocol Version 5 March 1996
authentication method to be used, authenticates with the chosen
method, then sends a relay request. The SOCKS server evaluates the
request, and either establishes the appropriate connection or denies
it.
Unless otherwise noted, the decimal numbers appearing in packet-
format diagrams represent the length of the corresponding field, in
octets. Where a given octet must take on a specific value, the
syntax X'hh' is used to denote the value of the single octet in that
field. When the word 'Variable' is used, it indicates that the
corresponding field has a variable length defined either by an
associated (one or two octet) length field, or by a data type field.
The client connects to the server, and sends a version
identifier/method selection message:
+----+----------+----------+
|VER | NMETHODS | METHODS |
+----+----------+----------+
| 1 | 1 | 1 to 255 |
+----+----------+----------+
The VER field is set to X'05' for this version of the protocol. The
NMETHODS field contains the number of method identifier octets that
appear in the METHODS field.
The server selects from one of the methods given in METHODS, and
sends a METHOD selection message:
+----+--------+
|VER | METHOD |
+----+--------+
| 1 | 1 |
+----+--------+
If the selected METHOD is X'FF', none of the methods listed by the
client are acceptable, and the client MUST close the connection.
The values currently defined for METHOD are:
o X'00' NO AUTHENTICATION REQUIRED
o X'01' GSSAPI
o X'02' USERNAME/PASSWORD
o X'03' to X'7F' IANA ASSIGNED
o X'80' to X'FE' RESERVED FOR PRIVATE METHODS
o X'FF' NO ACCEPTABLE METHODS
The client and server then enter a method-specific sub-negotiation.
Leech, et al Standards Track [Page 3]
RFC 1928 SOCKS Protocol Version 5 March 1996
Descriptions of the method-dependent sub-negotiations appear in
separate memos.
Developers of new METHOD support for this protocol should contact
IANA for a METHOD number. The ASSIGNED NUMBERS document should be
referred to for a current list of METHOD numbers and their
corresponding protocols.
Compliant implementations MUST support GSSAPI and SHOULD support
USERNAME/PASSWORD authentication methods.
4. Requests
Once the method-dependent subnegotiation has completed, the client
sends the request details. If the negotiated method includes
encapsulation for purposes of integrity checking and/or
confidentiality, these requests MUST be encapsulated in the method-
dependent encapsulation.
The SOCKS request is formed as follows:
+----+-----+-------+------+----------+----------+
|VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
Where:
o VER protocol version: X'05'
o CMD
o CONNECT X'01'
o BIND X'02'
o UDP ASSOCIATE X'03'
o RSV RESERVED
o ATYP address type of following address
o IP V4 address: X'01'
o DOMAINNAME: X'03'
o IP V6 address: X'04'
o DST.ADDR desired destination address
o DST.PORT desired destination port in network octet
order
The SOCKS server will typically evaluate the request based on source
and destination addresses, and return one or more reply messages, as
appropriate for the request type.
Leech, et al Standards Track [Page 4]
RFC 1928 SOCKS Protocol Version 5 March 1996
5. Addressing
In an address field (DST.ADDR, BND.ADDR), the ATYP field specifies
the type of address contained within the field:
o X'01'
the address is a version-4 IP address, with a length of 4 octets
o X'03'
the address field contains a fully-qualified domain name. The first
octet of the address field contains the number of octets of name that
follow, there is no terminating NUL octet.
o X'04'
the address is a version-6 IP address, with a length of 16 octets.
6. Replies
The SOCKS request information is sent by the client as soon as it has
established a connection to the SOCKS server, and completed the
authentication negotiations. The server evaluates the request, and
returns a reply formed as follows:
+----+-----+-------+------+----------+----------+
|VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
Where:
o VER protocol version: X'05'
o REP Reply field:
o X'00' succeeded
o X'01' general SOCKS server failure
o X'02' connection not allowed by ruleset
o X'03' Network unreachable
o X'04' Host unreachable
o X'05' Connection refused
o X'06' TTL expired
o X'07' Command not supported
o X'08' Address type not supported
o X'09' to X'FF' unassigned
o RSV RESERVED
o ATYP address type of following address
Leech, et al Standards Track [Page 5]
RFC 1928 SOCKS Protocol Version 5 March 1996
o IP V4 address: X'01'
o DOMAINNAME: X'03'
o IP V6 address: X'04'
o BND.ADDR server bound address
o BND.PORT server bound port in network octet order
Fields marked RESERVED (RSV) must be set to X'00'.
If the chosen method includes encapsulation for purposes of
authentication, integrity and/or confidentiality, the replies are
encapsulated in the method-dependent encapsulation.
CONNECT
In the reply to a CONNECT, BND.PORT contains the port number that the
server assigned to connect to the target host, while BND.ADDR
contains the associated IP address. The supplied BND.ADDR is often
different from the IP address that the client uses to reach the SOCKS
server, since such servers are often multi-homed. It is expected
that the SOCKS server will use DST.ADDR and DST.PORT, and the
client-side source address and port in evaluating the CONNECT
request.
BIND
The BIND request is used in protocols which require the client to
accept connections from the server. FTP is a well-known example,
which uses the primary client-to-server connection for commands and
status reports, but may use a server-to-client connection for
transferring data on demand (e.g. LS, GET, PUT).
It is expected that the client side of an application protocol will
use the BIND request only to establish secondary connections after a
primary connection is established using CONNECT. In is expected that
a SOCKS server will use DST.ADDR and DST.PORT in evaluating the BIND
request.
Two replies are sent from the SOCKS server to the client during a
BIND operation. The first is sent after the server creates and binds
a new socket. The BND.PORT field contains the port number that the
SOCKS server assigned to listen for an incoming connection. The
BND.ADDR field contains the associated IP address. The client will
typically use these pieces of information to notify (via the primary
or control connection) the application server of the rendezvous
address. The second reply occurs only after the anticipated incoming
connection succeeds or fails.
Leech, et al Standards Track [Page 6]
RFC 1928 SOCKS Protocol Version 5 March 1996
In the second reply, the BND.PORT and BND.ADDR fields contain the
address and port number of the connecting host.
UDP ASSOCIATE
The UDP ASSOCIATE request is used to establish an association within
the UDP relay process to handle UDP datagrams. The DST.ADDR and
DST.PORT fields contain the address and port that the client expects
to use to send UDP datagrams on for the association. The server MAY
use this information to limit access to the association. If the
client is not in possesion of the information at the time of the UDP
ASSOCIATE, the client MUST use a port number and address of all
zeros.
A UDP association terminates when the TCP connection that the UDP
ASSOCIATE request arrived on terminates.
In the reply to a UDP ASSOCIATE request, the BND.PORT and BND.ADDR
fields indicate the port number/address where the client MUST send
UDP request messages to be relayed.
Reply Processing
When a reply (REP value other than X'00') indicates a failure, the
SOCKS server MUST terminate the TCP connection shortly after sending
the reply. This must be no more than 10 seconds after detecting the
condition that caused a failure.
If the reply code (REP value of X'00') indicates a success, and the
request was either a BIND or a CONNECT, the client may now start
passing data. If the selected authentication method supports
encapsulation for the purposes of integrity, authentication and/or
confidentiality, the data are encapsulated using the method-dependent
encapsulation. Similarly, when data arrives at the SOCKS server for
the client, the server MUST encapsulate the data as appropriate for
the authentication method in use.
7. Procedure for UDP-based clients
A UDP-based client MUST send its datagrams to the UDP relay server at
the UDP port indicated by BND.PORT in the reply to the UDP ASSOCIATE
request. If the selected authentication method provides
encapsulation for the purposes of authenticity, integrity, and/or
confidentiality, the datagram MUST be encapsulated using the
appropriate encapsulation. Each UDP datagram carries a UDP request
header with it:
Leech, et al Standards Track [Page 7]
RFC 1928 SOCKS Protocol Version 5 March 1996
+----+------+------+----------+----------+----------+
|RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
+----+------+------+----------+----------+----------+
| 2 | 1 | 1 | Variable | 2 | Variable |
+----+------+------+----------+----------+----------+
The fields in the UDP request header are:
o RSV Reserved X'0000'
o FRAG Current fragment number
o ATYP address type of following addresses:
o IP V4 address: X'01'
o DOMAINNAME: X'03'
o IP V6 address: X'04'
o DST.ADDR desired destination address
o DST.PORT desired destination port
o DATA user data
When a UDP relay server decides to relay a UDP datagram, it does so
silently, without any notification to the requesting client.
Similarly, it will drop datagrams it cannot or will not relay. When
a UDP relay server receives a reply datagram from a remote host, it
MUST encapsulate that datagram using the above UDP request header,
and any authentication-method-dependent encapsulation.
The UDP relay server MUST acquire from the SOCKS server the expected
IP address of the client that will send datagrams to the BND.PORT
given in the reply to UDP ASSOCIATE. It MUST drop any datagrams
arriving from any source IP address other than the one recorded for
the particular association.
The FRAG field indicates whether or not this datagram is one of a
number of fragments. If implemented, the high-order bit indicates
end-of-fragment sequence, while a value of X'00' indicates that this
datagram is standalone. Values between 1 and 127 indicate the
fragment position within a fragment sequence. Each receiver will
have a REASSEMBLY QUEUE and a REASSEMBLY TIMER associated with these
fragments. The reassembly queue must be reinitialized and the
associated fragments abandoned whenever the REASSEMBLY TIMER expires,
or a new datagram arrives carrying a FRAG field whose value is less
than the highest FRAG value processed for this fragment sequence.
The reassembly timer MUST be no less than 5 seconds. It is
recommended that fragmentation be avoided by applications wherever
possible.
Implementation of fragmentation is optional; an implementation that
does not support fragmentation MUST drop any datagram whose FRAG
field is other than X'00'.
Leech, et al Standards Track [Page 8]
RFC 1928 SOCKS Protocol Version 5 March 1996
The programming interface for a SOCKS-aware UDP MUST report an
available buffer space for UDP datagrams that is smaller than the
actual space provided by the operating system:
o if ATYP is X'01' - 10+method_dependent octets smaller
o if ATYP is X'03' - 262+method_dependent octets smaller
o if ATYP is X'04' - 20+method_dependent octets smaller
8. Security Considerations
This document describes a protocol for the application-layer
traversal of IP network firewalls. The security of such traversal is
highly dependent on the particular authentication and encapsulation
methods provided in a particular implementation, and selected during
negotiation between SOCKS client and SOCKS server.
Careful consideration should be given by the administrator to the
selection of authentication methods.
9. References
[1] Koblas, D., "SOCKS", Proceedings: 1992 Usenix Security Symposium.
Author's Address
Marcus Leech
Bell-Northern Research Ltd
P.O. Box 3511, Stn. C,
Ottawa, ON
CANADA K1Y 4H7
Phone: (613) 763-9145
EMail: mleech@bnr.ca
Leech, et al Standards Track [Page 9]

View File

@ -1,115 +0,0 @@
Network Working Group M. Leech
Request for Comments: 1929 Bell-Northern Research Ltd
Category: Standards Track March 1996
Username/Password Authentication for SOCKS V5
Status of this Memo
This document specifies an Internet standards track protocol for the
Internet community, and requests discussion and suggestions for
improvements. Please refer to the current edition of the "Internet
Official Protocol Standards" (STD 1) for the standardization state
and status of this protocol. Distribution of this memo is unlimited.
1. Introduction
The protocol specification for SOCKS Version 5 specifies a
generalized framework for the use of arbitrary authentication
protocols in the initial socks connection setup. This document
describes one of those protocols, as it fits into the SOCKS Version 5
authentication "subnegotiation".
Note:
Unless otherwise noted, the decimal numbers appearing in packet-
format diagrams represent the length of the corresponding field, in
octets. Where a given octet must take on a specific value, the
syntax X'hh' is used to denote the value of the single octet in that
field. When the word 'Variable' is used, it indicates that the
corresponding field has a variable length defined either by an
associated (one or two octet) length field, or by a data type field.
2. Initial negotiation
Once the SOCKS V5 server has started, and the client has selected the
Username/Password Authentication protocol, the Username/Password
subnegotiation begins. This begins with the client producing a
Username/Password request:
+----+------+----------+------+----------+
|VER | ULEN | UNAME | PLEN | PASSWD |
+----+------+----------+------+----------+
| 1 | 1 | 1 to 255 | 1 | 1 to 255 |
+----+------+----------+------+----------+
Leech Standards Track [Page 1]
RFC 1929 Username Authentication for SOCKS V5 March 1996
The VER field contains the current version of the subnegotiation,
which is X'01'. The ULEN field contains the length of the UNAME field
that follows. The UNAME field contains the username as known to the
source operating system. The PLEN field contains the length of the
PASSWD field that follows. The PASSWD field contains the password
association with the given UNAME.
The server verifies the supplied UNAME and PASSWD, and sends the
following response:
+----+--------+
|VER | STATUS |
+----+--------+
| 1 | 1 |
+----+--------+
A STATUS field of X'00' indicates success. If the server returns a
`failure' (STATUS value other than X'00') status, it MUST close the
connection.
3. Security Considerations
This document describes a subnegotiation that provides authentication
services to the SOCKS protocol. Since the request carries the
password in cleartext, this subnegotiation is not recommended for
environments where "sniffing" is possible and practical.
4. Author's Address
Marcus Leech
Bell-Northern Research Ltd
P.O. Box 3511, Station C
Ottawa, ON
CANADA K1Y 4H7
Phone: +1 613 763 9145
EMail: mleech@bnr.ca
Leech Standards Track [Page 2]

View File

@ -1,673 +0,0 @@
// SOCKS Protocol Version 5
// http://tools.ietf.org/html/rfc1928
// http://tools.ietf.org/html/rfc1929
package gosocks5
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"strconv"
"sync"
)
const (
Ver5 = 5
UserPassVer = 1
)
const (
MethodNoAuth uint8 = iota
MethodGSSAPI
MethodUserPass
// X'03' to X'7F' IANA ASSIGNED
// X'80' to X'FE' RESERVED FOR PRIVATE METHODS
MethodNoAcceptable = 0xFF
)
const (
CmdConnect uint8 = 1
CmdBind = 2
CmdUdp = 3
)
const (
AddrIPv4 uint8 = 1
AddrDomain = 3
AddrIPv6 = 4
)
const (
Succeeded uint8 = iota
Failure
NotAllowed
NetUnreachable
HostUnreachable
ConnRefused
TTLExpired
CmdUnsupported
AddrUnsupported
)
var (
ErrBadVersion = errors.New("Bad version")
ErrBadFormat = errors.New("Bad format")
ErrBadAddrType = errors.New("Bad address type")
ErrShortBuffer = errors.New("Short buffer")
ErrBadMethod = errors.New("Bad method")
ErrAuthFailure = errors.New("Auth failure")
)
// buffer pools
var (
sPool = sync.Pool{
New: func() interface{} {
return make([]byte, 576)
},
} // small buff pool
lPool = sync.Pool{
New: func() interface{} {
return make([]byte, 64*1024+262)
},
} // large buff pool for udp
)
/*
Method selection
+----+----------+----------+
|VER | NMETHODS | METHODS |
+----+----------+----------+
| 1 | 1 | 1 to 255 |
+----+----------+----------+
*/
func ReadMethods(r io.Reader) ([]uint8, error) {
//b := make([]byte, 257)
b := sPool.Get().([]byte)
defer sPool.Put(b)
n, err := io.ReadAtLeast(r, b, 2)
if err != nil {
return nil, err
}
if b[0] != Ver5 {
return nil, ErrBadVersion
}
if b[1] == 0 {
return nil, ErrBadMethod
}
length := 2 + int(b[1])
if n < length {
if _, err := io.ReadFull(r, b[n:length]); err != nil {
return nil, err
}
}
methods := make([]byte, int(b[1]))
copy(methods, b[2:length])
return methods, nil
}
func WriteMethod(method uint8, w io.Writer) error {
_, err := w.Write([]byte{Ver5, method})
return err
}
/*
Username/Password authentication request
+----+------+----------+------+----------+
|VER | ULEN | UNAME | PLEN | PASSWD |
+----+------+----------+------+----------+
| 1 | 1 | 1 to 255 | 1 | 1 to 255 |
+----+------+----------+------+----------+
*/
type UserPassRequest struct {
Version byte
Username string
Password string
}
func NewUserPassRequest(ver byte, u, p string) *UserPassRequest {
return &UserPassRequest{
Version: ver,
Username: u,
Password: p,
}
}
func ReadUserPassRequest(r io.Reader) (*UserPassRequest, error) {
// b := make([]byte, 513)
b := sPool.Get().([]byte)
defer sPool.Put(b)
n, err := io.ReadAtLeast(r, b, 2)
if err != nil {
return nil, err
}
if b[0] != UserPassVer {
return nil, ErrBadVersion
}
req := &UserPassRequest{
Version: b[0],
}
ulen := int(b[1])
length := ulen + 3
if n < length {
if _, err := io.ReadFull(r, b[n:length]); err != nil {
return nil, err
}
n = length
}
req.Username = string(b[2 : 2+ulen])
plen := int(b[length-1])
length += plen
if n < length {
if _, err := io.ReadFull(r, b[n:length]); err != nil {
return nil, err
}
}
req.Password = string(b[3+ulen : length])
return req, nil
}
func (req *UserPassRequest) Write(w io.Writer) error {
// b := make([]byte, 513)
b := sPool.Get().([]byte)
defer sPool.Put(b)
b[0] = req.Version
ulen := len(req.Username)
b[1] = byte(ulen)
length := 2 + ulen
copy(b[2:length], req.Username)
plen := len(req.Password)
b[length] = byte(plen)
length++
copy(b[length:length+plen], req.Password)
length += plen
_, err := w.Write(b[:length])
return err
}
func (req *UserPassRequest) String() string {
return fmt.Sprintf("%d %s:%s",
req.Version, req.Username, req.Password)
}
/*
Username/Password authentication response
+----+--------+
|VER | STATUS |
+----+--------+
| 1 | 1 |
+----+--------+
*/
type UserPassResponse struct {
Version byte
Status byte
}
func NewUserPassResponse(ver, status byte) *UserPassResponse {
return &UserPassResponse{
Version: ver,
Status: status,
}
}
func ReadUserPassResponse(r io.Reader) (*UserPassResponse, error) {
// b := make([]byte, 2)
b := sPool.Get().([]byte)
defer sPool.Put(b)
if _, err := io.ReadFull(r, b[:2]); err != nil {
return nil, err
}
if b[0] != UserPassVer {
return nil, ErrBadVersion
}
res := &UserPassResponse{
Version: b[0],
Status: b[1],
}
return res, nil
}
func (res *UserPassResponse) Write(w io.Writer) error {
_, err := w.Write([]byte{res.Version, res.Status})
return err
}
func (res *UserPassResponse) String() string {
return fmt.Sprintf("%d %d",
res.Version, res.Status)
}
/*
Address
+------+----------+----------+
| ATYP | ADDR | PORT |
+------+----------+----------+
| 1 | Variable | 2 |
+------+----------+----------+
*/
type Addr struct {
Type uint8
Host string
Port uint16
}
func (addr *Addr) Decode(b []byte) error {
addr.Type = b[0]
pos := 1
switch addr.Type {
case AddrIPv4:
addr.Host = net.IP(b[pos : pos+net.IPv4len]).String()
pos += net.IPv4len
case AddrIPv6:
addr.Host = net.IP(b[pos : pos+net.IPv6len]).String()
pos += net.IPv6len
case AddrDomain:
addrlen := int(b[pos])
pos++
addr.Host = string(b[pos : pos+addrlen])
pos += addrlen
default:
return ErrBadAddrType
}
addr.Port = binary.BigEndian.Uint16(b[pos:])
return nil
}
func (addr *Addr) Encode(b []byte) (int, error) {
b[0] = addr.Type
pos := 1
switch addr.Type {
case AddrIPv4:
ip4 := net.ParseIP(addr.Host).To4()
if ip4 == nil {
ip4 = net.IPv4zero.To4()
}
pos += copy(b[pos:], ip4)
case AddrDomain:
b[pos] = byte(len(addr.Host))
pos++
pos += copy(b[pos:], []byte(addr.Host))
case AddrIPv6:
ip16 := net.ParseIP(addr.Host).To16()
if ip16 == nil {
ip16 = net.IPv6zero.To16()
}
pos += copy(b[pos:], ip16)
default:
b[0] = AddrIPv4
copy(b[pos:pos+4], net.IPv4zero.To4())
pos += 4
}
binary.BigEndian.PutUint16(b[pos:], addr.Port)
pos += 2
return pos, nil
}
func (addr *Addr) Length() (n int) {
switch addr.Type {
case AddrIPv4:
n = 10
case AddrIPv6:
n = 22
case AddrDomain:
n = 7 + len(addr.Host)
default:
n = 10
}
return
}
func (addr *Addr) String() string {
return net.JoinHostPort(addr.Host, strconv.Itoa(int(addr.Port)))
}
/*
The SOCKSv5 request
+----+-----+-------+------+----------+----------+
|VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
*/
type Request struct {
Cmd uint8
Addr *Addr
}
func NewRequest(cmd uint8, addr *Addr) *Request {
return &Request{
Cmd: cmd,
Addr: addr,
}
}
func ReadRequest(r io.Reader) (*Request, error) {
// b := make([]byte, 262)
b := sPool.Get().([]byte)
defer sPool.Put(b)
n, err := io.ReadAtLeast(r, b, 5)
if err != nil {
return nil, err
}
if b[0] != Ver5 {
return nil, ErrBadVersion
}
request := &Request{
Cmd: b[1],
}
atype := b[3]
length := 0
switch atype {
case AddrIPv4:
length = 10
case AddrIPv6:
length = 22
case AddrDomain:
length = 7 + int(b[4])
default:
return nil, ErrBadAddrType
}
if n < length {
if _, err := io.ReadFull(r, b[n:length]); err != nil {
return nil, err
}
}
addr := new(Addr)
if err := addr.Decode(b[3:length]); err != nil {
return nil, err
}
request.Addr = addr
return request, nil
}
func (r *Request) Write(w io.Writer) (err error) {
//b := make([]byte, 262)
b := sPool.Get().([]byte)
defer sPool.Put(b)
b[0] = Ver5
b[1] = r.Cmd
b[2] = 0 //rsv
b[3] = AddrIPv4 // default
addr := r.Addr
if addr == nil {
addr = &Addr{}
}
n, _ := addr.Encode(b[3:])
length := 3 + n
_, err = w.Write(b[:length])
return
}
func (r *Request) String() string {
addr := r.Addr
if addr == nil {
addr = &Addr{}
}
return fmt.Sprintf("5 %d 0 %d %s",
r.Cmd, addr.Type, addr.String())
}
/*
The SOCKSv5 reply
+----+-----+-------+------+----------+----------+
|VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
*/
type Reply struct {
Rep uint8
Addr *Addr
}
func NewReply(rep uint8, addr *Addr) *Reply {
return &Reply{
Rep: rep,
Addr: addr,
}
}
func ReadReply(r io.Reader) (*Reply, error) {
// b := make([]byte, 262)
b := sPool.Get().([]byte)
defer sPool.Put(b)
n, err := io.ReadAtLeast(r, b, 5)
if err != nil {
return nil, err
}
if b[0] != Ver5 {
return nil, ErrBadVersion
}
reply := &Reply{
Rep: b[1],
}
atype := b[3]
length := 0
switch atype {
case AddrIPv4:
length = 10
case AddrIPv6:
length = 22
case AddrDomain:
length = 7 + int(b[4])
default:
return nil, ErrBadAddrType
}
if n < length {
if _, err := io.ReadFull(r, b[n:length]); err != nil {
return nil, err
}
}
addr := new(Addr)
if err := addr.Decode(b[3:length]); err != nil {
return nil, err
}
reply.Addr = addr
return reply, nil
}
func (r *Reply) Write(w io.Writer) (err error) {
// b := make([]byte, 262)
b := sPool.Get().([]byte)
defer sPool.Put(b)
b[0] = Ver5
b[1] = r.Rep
b[2] = 0 //rsv
b[3] = AddrIPv4 // default
length := 10
b[4], b[5], b[6], b[7], b[8], b[9] = 0, 0, 0, 0, 0, 0 // reset address field
if r.Addr != nil {
n, _ := r.Addr.Encode(b[3:])
length = 3 + n
}
_, err = w.Write(b[:length])
return
}
func (r *Reply) String() string {
addr := r.Addr
if addr == nil {
addr = &Addr{}
}
return fmt.Sprintf("5 %d 0 %d %s",
r.Rep, addr.Type, addr.String())
}
/*
UDP request
+----+------+------+----------+----------+----------+
|RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
+----+------+------+----------+----------+----------+
| 2 | 1 | 1 | Variable | 2 | Variable |
+----+------+------+----------+----------+----------+
*/
type UDPHeader struct {
Rsv uint16
Frag uint8
Addr *Addr
}
func NewUDPHeader(rsv uint16, frag uint8, addr *Addr) *UDPHeader {
return &UDPHeader{
Rsv: rsv,
Frag: frag,
Addr: addr,
}
}
func (h *UDPHeader) Write(w io.Writer) error {
b := sPool.Get().([]byte)
defer sPool.Put(b)
binary.BigEndian.PutUint16(b[:2], h.Rsv)
b[2] = h.Frag
addr := h.Addr
if addr == nil {
addr = &Addr{}
}
length, _ := addr.Encode(b[3:])
_, err := w.Write(b[:3+length])
return err
}
func (h *UDPHeader) String() string {
return fmt.Sprintf("%d %d %d %s",
h.Rsv, h.Frag, h.Addr.Type, h.Addr.String())
}
type UDPDatagram struct {
Header *UDPHeader
Data []byte
}
func NewUDPDatagram(header *UDPHeader, data []byte) *UDPDatagram {
return &UDPDatagram{
Header: header,
Data: data,
}
}
func ReadUDPDatagram(r io.Reader) (*UDPDatagram, error) {
b := lPool.Get().([]byte)
defer lPool.Put(b)
// when r is a streaming (such as TCP connection), we may read more than the required data,
// but we don't know how to handle it. So we use io.ReadFull to instead of io.ReadAtLeast
// to make sure that no redundant data will be discarded.
n, err := io.ReadFull(r, b[:5])
if err != nil {
return nil, err
}
header := &UDPHeader{
Rsv: binary.BigEndian.Uint16(b[:2]),
Frag: b[2],
}
atype := b[3]
hlen := 0
switch atype {
case AddrIPv4:
hlen = 10
case AddrIPv6:
hlen = 22
case AddrDomain:
hlen = 7 + int(b[4])
default:
return nil, ErrBadAddrType
}
dlen := int(header.Rsv)
if dlen == 0 { // standard SOCKS5 UDP datagram
extra, err := ioutil.ReadAll(r) // we assume no redundant data
if err != nil {
return nil, err
}
copy(b[n:], extra)
n += len(extra) // total length
dlen = n - hlen // data length
} else { // extended feature, for UDP over TCP, using reserved field as data length
if _, err := io.ReadFull(r, b[n:hlen+dlen]); err != nil {
return nil, err
}
n = hlen + dlen
}
header.Addr = new(Addr)
if err := header.Addr.Decode(b[3:hlen]); err != nil {
return nil, err
}
data := make([]byte, dlen)
copy(data, b[hlen:n])
d := &UDPDatagram{
Header: header,
Data: data,
}
return d, nil
}
func (d *UDPDatagram) Write(w io.Writer) error {
h := d.Header
if h == nil {
h = &UDPHeader{}
}
buf := bytes.Buffer{}
if err := h.Write(&buf); err != nil {
return err
}
if _, err := buf.Write(d.Data); err != nil {
return err
}
_, err := buf.WriteTo(w)
return err
}

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2017 ginuerzh
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,2 +0,0 @@
# tls-dissector
Dissect TLS handshake messages

View File

@ -1,85 +0,0 @@
package dissector
import (
"bytes"
"encoding/binary"
"io"
)
const (
ExtServerName uint16 = 0x0000
)
type Extension interface {
Type() uint16
Bytes() []byte
}
func ReadExtension(r io.Reader) (ext Extension, err error) {
b := make([]byte, 4)
if _, err = io.ReadFull(r, b); err != nil {
return
}
bb := make([]byte, int(binary.BigEndian.Uint16(b[2:4])))
if _, err = io.ReadFull(r, bb); err != nil {
return nil, err
}
t := binary.BigEndian.Uint16(b[:2])
switch t {
case ExtServerName:
ext = &ServerNameExtension{
NameType: bb[2],
Name: string(bb[5:]),
}
default:
ext = &unknownExtension{
raw: append(b, bb...),
}
}
return
}
type unknownExtension struct {
raw []byte
}
func NewExtension(t uint16, data []byte) Extension {
ext := &unknownExtension{
raw: make([]byte, 2+2+len(data)),
}
binary.BigEndian.PutUint16(ext.raw[:2], t)
binary.BigEndian.PutUint16(ext.raw[2:4], uint16(len(data)))
copy(ext.raw[4:], data)
return ext
}
func (ext *unknownExtension) Type() uint16 {
return binary.BigEndian.Uint16(ext.raw)
}
func (ext *unknownExtension) Bytes() []byte {
return ext.raw
}
type ServerNameExtension struct {
NameType uint8
Name string
}
func (ext *ServerNameExtension) Type() uint16 {
return ExtServerName
}
func (ext *ServerNameExtension) Bytes() []byte {
buf := &bytes.Buffer{}
binary.Write(buf, binary.BigEndian, ExtServerName)
binary.Write(buf, binary.BigEndian, uint16(2+1+2+len(ext.Name)))
binary.Write(buf, binary.BigEndian, uint16(1+2+len(ext.Name)))
buf.WriteByte(ext.NameType)
binary.Write(buf, binary.BigEndian, uint16(len(ext.Name)))
buf.WriteString(ext.Name)
return buf.Bytes()
}

View File

@ -1,238 +0,0 @@
package dissector
import (
"bytes"
"crypto/tls"
"encoding/binary"
"fmt"
"io"
)
const (
handshakeHeaderLen = 4
)
const (
HelloRequest = 0
ClientHello = 1
ServerHello = 2
)
type Random struct {
Time uint32
Opaque [28]byte
}
type CipherSuite uint16
type CompressionMethod uint8
type ClientHelloHandshake struct {
Version Version
Random Random
SessionID []byte
CipherSuites []CipherSuite
CompressionMethods []CompressionMethod
Extensions []Extension
}
func (h *ClientHelloHandshake) Encode() (data []byte, err error) {
buf := new(bytes.Buffer)
if _, err = h.WriteTo(buf); err != nil {
return
}
data = buf.Bytes()
return
}
func (h *ClientHelloHandshake) Decode(data []byte) (err error) {
_, err = h.ReadFrom(bytes.NewReader(data))
return
}
func (h *ClientHelloHandshake) ReadFrom(r io.Reader) (n int64, err error) {
b := make([]byte, handshakeHeaderLen)
nn, err := io.ReadFull(r, b)
n += int64(nn)
if err != nil {
return
}
if b[0] != ClientHello {
err = ErrBadType
return
}
length := int(b[1])<<16 | int(b[2])<<8 | int(b[3])
if length < 34 { // length of version + random
err = fmt.Errorf("bad length, need at least 34 bytes, got %d", length)
return
}
b = make([]byte, length)
nn, err = io.ReadFull(r, b)
n += int64(nn)
if err != nil {
return
}
h.Version = Version(binary.BigEndian.Uint16(b[:2]))
if h.Version < tls.VersionTLS12 {
err = fmt.Errorf("bad version: only TLSv1.2 is supported")
return
}
pos := 2
h.Random.Time = binary.BigEndian.Uint32(b[pos : pos+4])
pos += 4
copy(h.Random.Opaque[:], b[pos:pos+28])
pos += 28
nn, err = h.readSession(b[pos:])
if err != nil {
return
}
pos += nn
nn, err = h.readCipherSuites(b[pos:])
if err != nil {
return
}
pos += nn
nn, err = h.readCompressionMethods(b[pos:])
if err != nil {
return
}
pos += nn
nn, err = h.readExtensions(b[pos:])
if err != nil {
return
}
// pos += nn
return
}
func (h *ClientHelloHandshake) readSession(b []byte) (n int, err error) {
if len(b) == 0 {
err = fmt.Errorf("bad length: data too short for session")
return
}
nlen := int(b[0])
n++
if len(b) < n+nlen {
err = fmt.Errorf("bad length: malformed data for session")
}
if nlen > 0 && n+nlen <= len(b) {
h.SessionID = make([]byte, nlen)
copy(h.SessionID, b[n:n+nlen])
n += nlen
}
return
}
func (h *ClientHelloHandshake) readCipherSuites(b []byte) (n int, err error) {
if len(b) < 2 {
err = fmt.Errorf("bad length: data too short for cipher suites")
return
}
nlen := int(binary.BigEndian.Uint16(b[:2]))
n += 2
if len(b) < n+nlen {
err = fmt.Errorf("bad length: malformed data for cipher suites")
}
for i := 0; i < nlen/2; i++ {
h.CipherSuites = append(h.CipherSuites, CipherSuite(binary.BigEndian.Uint16(b[n:n+2])))
n += 2
}
return
}
func (h *ClientHelloHandshake) readCompressionMethods(b []byte) (n int, err error) {
if len(b) == 0 {
err = fmt.Errorf("bad length: data too short for compression methods")
return
}
nlen := int(b[0])
n++
if len(b) < n+nlen {
err = fmt.Errorf("bad length: malformed data for compression methods")
}
for i := 0; i < nlen; i++ {
h.CompressionMethods = append(h.CompressionMethods, CompressionMethod(b[n]))
n++
}
return
}
func (h *ClientHelloHandshake) readExtensions(b []byte) (n int, err error) {
if len(b) < 2 {
err = fmt.Errorf("bad length: data too short for extensions")
return
}
nlen := int(binary.BigEndian.Uint16(b[:2]))
n += 2
if len(b) < n+nlen {
err = fmt.Errorf("bad length: malformed data for extensions")
return
}
br := bytes.NewReader(b[n:])
for br.Len() > 0 {
cn := br.Len()
var ext Extension
ext, err = ReadExtension(br)
if err != nil {
return
}
h.Extensions = append(h.Extensions, ext)
n += (cn - br.Len())
}
return
}
func (h *ClientHelloHandshake) WriteTo(w io.Writer) (n int64, err error) {
buf := &bytes.Buffer{}
buf.WriteByte(ClientHello)
buf.Write([]byte{0, 0, 0}) // placeholder for payload length
binary.Write(buf, binary.BigEndian, h.Version)
pos := 6
binary.Write(buf, binary.BigEndian, h.Random.Time)
buf.Write(h.Random.Opaque[:])
pos += 32
buf.WriteByte(byte(len(h.SessionID)))
buf.Write(h.SessionID)
pos += (1 + len(h.SessionID))
binary.Write(buf, binary.BigEndian, uint16(len(h.CipherSuites)*2))
for _, cs := range h.CipherSuites {
binary.Write(buf, binary.BigEndian, cs)
}
pos += (2 + len(h.CipherSuites)*2)
buf.WriteByte(byte(len(h.CompressionMethods)))
for _, cm := range h.CompressionMethods {
buf.WriteByte(byte(cm))
}
pos += (1 + len(h.CompressionMethods))
buf.Write([]byte{0, 0}) // placeholder for extensions length
extLen := 0
for _, ext := range h.Extensions {
nn, _ := buf.Write(ext.Bytes())
extLen += nn
}
b := buf.Bytes()
plen := len(b) - handshakeHeaderLen
b[1], b[2], b[3] = byte((plen>>16)&0xFF), byte((plen>>8)&0xFF), byte(plen&0xFF) // payload length
b[pos], b[pos+1] = byte((extLen>>8)&0xFF), byte(extLen&0xFF) // extensions length
nn, err := w.Write(b)
n = int64(nn)
return
}

View File

@ -1,61 +0,0 @@
package dissector
import (
"bytes"
"encoding/binary"
"errors"
"io"
)
const (
RecordHeaderLen = 5
)
const (
Handshake = 0x16
)
var (
ErrBadType = errors.New("bad type")
)
type Version uint16
type Record struct {
Type uint8
Version Version
Opaque []byte
}
func ReadRecord(r io.Reader) (*Record, error) {
record := &Record{}
if _, err := record.ReadFrom(r); err != nil {
return nil, err
}
return record, nil
}
func (rec *Record) ReadFrom(r io.Reader) (n int64, err error) {
b := make([]byte, RecordHeaderLen)
nn, err := io.ReadFull(r, b)
n += int64(nn)
if err != nil {
return
}
rec.Type = b[0]
rec.Version = Version(binary.BigEndian.Uint16(b[1:3]))
length := int(binary.BigEndian.Uint16(b[3:5]))
rec.Opaque = make([]byte, length)
nn, err = io.ReadFull(r, rec.Opaque)
n += int64(nn)
return
}
func (rec *Record) WriteTo(w io.Writer) (n int64, err error) {
buf := &bytes.Buffer{}
buf.WriteByte(rec.Type)
binary.Write(buf, binary.BigEndian, rec.Version)
binary.Write(buf, binary.BigEndian, uint16(len(rec.Opaque)))
buf.Write(rec.Opaque)
return buf.WriteTo(w)
}

File diff suppressed because it is too large Load Diff

19
vendor/github.com/go-log/log/LICENSE generated vendored
View File

@ -1,19 +0,0 @@
Copyright (c) 2017 Go Log
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,63 +0,0 @@
# Log [![GoDoc](https://godoc.org/github.com/go-log/log?status.svg)](https://godoc.org/github.com/go-log/log)
Log is a logging interface for Go. That's it. Pass around the interface.
## Rationale
Users want to standardise logging. Sometimes libraries log. We leave the underlying logging implementation to the user
while allowing libraries to log by simply expecting something that satisfies the Logger interface. This leaves
the user free to pre-configure structure, output, etc.
## Interface
The interface is minimalistic on purpose
```go
type Logger interface {
Log(v ...interface{})
Logf(format string, v ...interface{})
}
```
## Example
Here's a logger that uses logrus and logs with predefined fields.
```go
import (
"github.com/go-log/log"
"github.com/sirupsen/logrus"
)
type logrusLogger struct {
*logrus.Entry
}
func (l *logrusLogger) Log(v ...interface{}) {
l.Entry.Print(v...)
}
func (l *logrusLogger) Logf(format string, v ...interface{}) {
l.Entry.Printf(format, v...)
}
func WithFields(f logrus.Fields) log.Logger {
return &logrusLogger{logrus.WithFields(f)}
}
```
The `WithFields` func returns a struct that satisfies the Logger interface.
Pre-configure a logger using WithFields and pass it as an option to a library.
```go
import "github.com/lib/foo"
l := mylogger.WithFields(logrus.Fields{
"library": "github.com/lib/foo",
})
f := foo.New(
foo.WithLogger(l),
)
```

30
vendor/github.com/go-log/log/log.go generated vendored
View File

@ -1,30 +0,0 @@
// Package log provides a log interface
package log
// Logger is a generic logging interface
type Logger interface {
Log(v ...interface{})
Logf(format string, v ...interface{})
}
var (
// The global default logger
DefaultLogger Logger = &noOpLogger{}
)
// noOpLogger is used as a placeholder for the default logger
type noOpLogger struct{}
func (n *noOpLogger) Log(v ...interface{}) {}
func (n *noOpLogger) Logf(format string, v ...interface{}) {}
// Log logs using the default logger
func Log(v ...interface{}) {
DefaultLogger.Log(v...)
}
// Logf logs formatted using the default logger
func Logf(format string, v ...interface{}) {
DefaultLogger.Logf(format, v...)
}

View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2016 Sergey Kamardin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,26 +0,0 @@
#! /bin/bash
bench() {
filename="/tmp/$1-$2.bench"
if test -e "${filename}";
then
echo "Already exists ${filename}"
else
backup=`git rev-parse --abbrev-ref HEAD`
git checkout $1
echo -n "Creating ${filename}... "
go test ./... -run=NONE -bench=$2 > "${filename}" -benchmem
echo "OK"
git checkout ${backup}
sleep 5
fi
}
to=$1
current=`git rev-parse --abbrev-ref HEAD`
bench ${to} $2
bench ${current} $2
benchcmp $3 "/tmp/${to}-$2.bench" "/tmp/${current}-$2.bench"

View File

@ -1,525 +0,0 @@
package compiler
// TODO use constructor with all matchers, and to their structs private
// TODO glue multiple Text nodes (like after QuoteMeta)
import (
"fmt"
"reflect"
"github.com/gobwas/glob/match"
"github.com/gobwas/glob/syntax/ast"
"github.com/gobwas/glob/util/runes"
)
func optimizeMatcher(matcher match.Matcher) match.Matcher {
switch m := matcher.(type) {
case match.Any:
if len(m.Separators) == 0 {
return match.NewSuper()
}
case match.AnyOf:
if len(m.Matchers) == 1 {
return m.Matchers[0]
}
return m
case match.List:
if m.Not == false && len(m.List) == 1 {
return match.NewText(string(m.List))
}
return m
case match.BTree:
m.Left = optimizeMatcher(m.Left)
m.Right = optimizeMatcher(m.Right)
r, ok := m.Value.(match.Text)
if !ok {
return m
}
var (
leftNil = m.Left == nil
rightNil = m.Right == nil
)
if leftNil && rightNil {
return match.NewText(r.Str)
}
_, leftSuper := m.Left.(match.Super)
lp, leftPrefix := m.Left.(match.Prefix)
la, leftAny := m.Left.(match.Any)
_, rightSuper := m.Right.(match.Super)
rs, rightSuffix := m.Right.(match.Suffix)
ra, rightAny := m.Right.(match.Any)
switch {
case leftSuper && rightSuper:
return match.NewContains(r.Str, false)
case leftSuper && rightNil:
return match.NewSuffix(r.Str)
case rightSuper && leftNil:
return match.NewPrefix(r.Str)
case leftNil && rightSuffix:
return match.NewPrefixSuffix(r.Str, rs.Suffix)
case rightNil && leftPrefix:
return match.NewPrefixSuffix(lp.Prefix, r.Str)
case rightNil && leftAny:
return match.NewSuffixAny(r.Str, la.Separators)
case leftNil && rightAny:
return match.NewPrefixAny(r.Str, ra.Separators)
}
return m
}
return matcher
}
func compileMatchers(matchers []match.Matcher) (match.Matcher, error) {
if len(matchers) == 0 {
return nil, fmt.Errorf("compile error: need at least one matcher")
}
if len(matchers) == 1 {
return matchers[0], nil
}
if m := glueMatchers(matchers); m != nil {
return m, nil
}
idx := -1
maxLen := -1
var val match.Matcher
for i, matcher := range matchers {
if l := matcher.Len(); l != -1 && l >= maxLen {
maxLen = l
idx = i
val = matcher
}
}
if val == nil { // not found matcher with static length
r, err := compileMatchers(matchers[1:])
if err != nil {
return nil, err
}
return match.NewBTree(matchers[0], nil, r), nil
}
left := matchers[:idx]
var right []match.Matcher
if len(matchers) > idx+1 {
right = matchers[idx+1:]
}
var l, r match.Matcher
var err error
if len(left) > 0 {
l, err = compileMatchers(left)
if err != nil {
return nil, err
}
}
if len(right) > 0 {
r, err = compileMatchers(right)
if err != nil {
return nil, err
}
}
return match.NewBTree(val, l, r), nil
}
func glueMatchers(matchers []match.Matcher) match.Matcher {
if m := glueMatchersAsEvery(matchers); m != nil {
return m
}
if m := glueMatchersAsRow(matchers); m != nil {
return m
}
return nil
}
func glueMatchersAsRow(matchers []match.Matcher) match.Matcher {
if len(matchers) <= 1 {
return nil
}
var (
c []match.Matcher
l int
)
for _, matcher := range matchers {
if ml := matcher.Len(); ml == -1 {
return nil
} else {
c = append(c, matcher)
l += ml
}
}
return match.NewRow(l, c...)
}
func glueMatchersAsEvery(matchers []match.Matcher) match.Matcher {
if len(matchers) <= 1 {
return nil
}
var (
hasAny bool
hasSuper bool
hasSingle bool
min int
separator []rune
)
for i, matcher := range matchers {
var sep []rune
switch m := matcher.(type) {
case match.Super:
sep = []rune{}
hasSuper = true
case match.Any:
sep = m.Separators
hasAny = true
case match.Single:
sep = m.Separators
hasSingle = true
min++
case match.List:
if !m.Not {
return nil
}
sep = m.List
hasSingle = true
min++
default:
return nil
}
// initialize
if i == 0 {
separator = sep
}
if runes.Equal(sep, separator) {
continue
}
return nil
}
if hasSuper && !hasAny && !hasSingle {
return match.NewSuper()
}
if hasAny && !hasSuper && !hasSingle {
return match.NewAny(separator)
}
if (hasAny || hasSuper) && min > 0 && len(separator) == 0 {
return match.NewMin(min)
}
every := match.NewEveryOf()
if min > 0 {
every.Add(match.NewMin(min))
if !hasAny && !hasSuper {
every.Add(match.NewMax(min))
}
}
if len(separator) > 0 {
every.Add(match.NewContains(string(separator), true))
}
return every
}
func minimizeMatchers(matchers []match.Matcher) []match.Matcher {
var done match.Matcher
var left, right, count int
for l := 0; l < len(matchers); l++ {
for r := len(matchers); r > l; r-- {
if glued := glueMatchers(matchers[l:r]); glued != nil {
var swap bool
if done == nil {
swap = true
} else {
cl, gl := done.Len(), glued.Len()
swap = cl > -1 && gl > -1 && gl > cl
swap = swap || count < r-l
}
if swap {
done = glued
left = l
right = r
count = r - l
}
}
}
}
if done == nil {
return matchers
}
next := append(append([]match.Matcher{}, matchers[:left]...), done)
if right < len(matchers) {
next = append(next, matchers[right:]...)
}
if len(next) == len(matchers) {
return next
}
return minimizeMatchers(next)
}
// minimizeAnyOf tries to apply some heuristics to minimize number of nodes in given tree
func minimizeTree(tree *ast.Node) *ast.Node {
switch tree.Kind {
case ast.KindAnyOf:
return minimizeTreeAnyOf(tree)
default:
return nil
}
}
// minimizeAnyOf tries to find common children of given node of AnyOf pattern
// it searches for common children from left and from right
// if any common children are found then it returns new optimized ast tree
// else it returns nil
func minimizeTreeAnyOf(tree *ast.Node) *ast.Node {
if !areOfSameKind(tree.Children, ast.KindPattern) {
return nil
}
commonLeft, commonRight := commonChildren(tree.Children)
commonLeftCount, commonRightCount := len(commonLeft), len(commonRight)
if commonLeftCount == 0 && commonRightCount == 0 { // there are no common parts
return nil
}
var result []*ast.Node
if commonLeftCount > 0 {
result = append(result, ast.NewNode(ast.KindPattern, nil, commonLeft...))
}
var anyOf []*ast.Node
for _, child := range tree.Children {
reuse := child.Children[commonLeftCount : len(child.Children)-commonRightCount]
var node *ast.Node
if len(reuse) == 0 {
// this pattern is completely reduced by commonLeft and commonRight patterns
// so it become nothing
node = ast.NewNode(ast.KindNothing, nil)
} else {
node = ast.NewNode(ast.KindPattern, nil, reuse...)
}
anyOf = appendIfUnique(anyOf, node)
}
switch {
case len(anyOf) == 1 && anyOf[0].Kind != ast.KindNothing:
result = append(result, anyOf[0])
case len(anyOf) > 1:
result = append(result, ast.NewNode(ast.KindAnyOf, nil, anyOf...))
}
if commonRightCount > 0 {
result = append(result, ast.NewNode(ast.KindPattern, nil, commonRight...))
}
return ast.NewNode(ast.KindPattern, nil, result...)
}
func commonChildren(nodes []*ast.Node) (commonLeft, commonRight []*ast.Node) {
if len(nodes) <= 1 {
return
}
// find node that has least number of children
idx := leastChildren(nodes)
if idx == -1 {
return
}
tree := nodes[idx]
treeLength := len(tree.Children)
// allocate max able size for rightCommon slice
// to get ability insert elements in reverse order (from end to start)
// without sorting
commonRight = make([]*ast.Node, treeLength)
lastRight := treeLength // will use this to get results as commonRight[lastRight:]
var (
breakLeft bool
breakRight bool
commonTotal int
)
for i, j := 0, treeLength-1; commonTotal < treeLength && j >= 0 && !(breakLeft && breakRight); i, j = i+1, j-1 {
treeLeft := tree.Children[i]
treeRight := tree.Children[j]
for k := 0; k < len(nodes) && !(breakLeft && breakRight); k++ {
// skip least children node
if k == idx {
continue
}
restLeft := nodes[k].Children[i]
restRight := nodes[k].Children[j+len(nodes[k].Children)-treeLength]
breakLeft = breakLeft || !treeLeft.Equal(restLeft)
// disable searching for right common parts, if left part is already overlapping
breakRight = breakRight || (!breakLeft && j <= i)
breakRight = breakRight || !treeRight.Equal(restRight)
}
if !breakLeft {
commonTotal++
commonLeft = append(commonLeft, treeLeft)
}
if !breakRight {
commonTotal++
lastRight = j
commonRight[j] = treeRight
}
}
commonRight = commonRight[lastRight:]
return
}
func appendIfUnique(target []*ast.Node, val *ast.Node) []*ast.Node {
for _, n := range target {
if reflect.DeepEqual(n, val) {
return target
}
}
return append(target, val)
}
func areOfSameKind(nodes []*ast.Node, kind ast.Kind) bool {
for _, n := range nodes {
if n.Kind != kind {
return false
}
}
return true
}
func leastChildren(nodes []*ast.Node) int {
min := -1
idx := -1
for i, n := range nodes {
if idx == -1 || (len(n.Children) < min) {
min = len(n.Children)
idx = i
}
}
return idx
}
func compileTreeChildren(tree *ast.Node, sep []rune) ([]match.Matcher, error) {
var matchers []match.Matcher
for _, desc := range tree.Children {
m, err := compile(desc, sep)
if err != nil {
return nil, err
}
matchers = append(matchers, optimizeMatcher(m))
}
return matchers, nil
}
func compile(tree *ast.Node, sep []rune) (m match.Matcher, err error) {
switch tree.Kind {
case ast.KindAnyOf:
// todo this could be faster on pattern_alternatives_combine_lite (see glob_test.go)
if n := minimizeTree(tree); n != nil {
return compile(n, sep)
}
matchers, err := compileTreeChildren(tree, sep)
if err != nil {
return nil, err
}
return match.NewAnyOf(matchers...), nil
case ast.KindPattern:
if len(tree.Children) == 0 {
return match.NewNothing(), nil
}
matchers, err := compileTreeChildren(tree, sep)
if err != nil {
return nil, err
}
m, err = compileMatchers(minimizeMatchers(matchers))
if err != nil {
return nil, err
}
case ast.KindAny:
m = match.NewAny(sep)
case ast.KindSuper:
m = match.NewSuper()
case ast.KindSingle:
m = match.NewSingle(sep)
case ast.KindNothing:
m = match.NewNothing()
case ast.KindList:
l := tree.Value.(ast.List)
m = match.NewList([]rune(l.Chars), l.Not)
case ast.KindRange:
r := tree.Value.(ast.Range)
m = match.NewRange(r.Lo, r.Hi, r.Not)
case ast.KindText:
t := tree.Value.(ast.Text)
m = match.NewText(t.Text)
default:
return nil, fmt.Errorf("could not compile tree: unknown node type")
}
return optimizeMatcher(m), nil
}
func Compile(tree *ast.Node, sep []rune) (match.Matcher, error) {
m, err := compile(tree, sep)
if err != nil {
return nil, err
}
return m, nil
}

View File

@ -1,80 +0,0 @@
package glob
import (
"github.com/gobwas/glob/compiler"
"github.com/gobwas/glob/syntax"
)
// Glob represents compiled glob pattern.
type Glob interface {
Match(string) bool
}
// Compile creates Glob for given pattern and strings (if any present after pattern) as separators.
// The pattern syntax is:
//
// pattern:
// { term }
//
// term:
// `*` matches any sequence of non-separator characters
// `**` matches any sequence of characters
// `?` matches any single non-separator character
// `[` [ `!` ] { character-range } `]`
// character class (must be non-empty)
// `{` pattern-list `}`
// pattern alternatives
// c matches character c (c != `*`, `**`, `?`, `\`, `[`, `{`, `}`)
// `\` c matches character c
//
// character-range:
// c matches character c (c != `\\`, `-`, `]`)
// `\` c matches character c
// lo `-` hi matches character c for lo <= c <= hi
//
// pattern-list:
// pattern { `,` pattern }
// comma-separated (without spaces) patterns
//
func Compile(pattern string, separators ...rune) (Glob, error) {
ast, err := syntax.Parse(pattern)
if err != nil {
return nil, err
}
matcher, err := compiler.Compile(ast, separators)
if err != nil {
return nil, err
}
return matcher, nil
}
// MustCompile is the same as Compile, except that if Compile returns error, this will panic
func MustCompile(pattern string, separators ...rune) Glob {
g, err := Compile(pattern, separators...)
if err != nil {
panic(err)
}
return g
}
// QuoteMeta returns a string that quotes all glob pattern meta characters
// inside the argument text; For example, QuoteMeta(`{foo*}`) returns `\[foo\*\]`.
func QuoteMeta(s string) string {
b := make([]byte, 2*len(s))
// a byte loop is correct because all meta characters are ASCII
j := 0
for i := 0; i < len(s); i++ {
if syntax.Special(s[i]) {
b[j] = '\\'
j++
}
b[j] = s[i]
j++
}
return string(b[0:j])
}

View File

@ -1,45 +0,0 @@
package match
import (
"fmt"
"github.com/gobwas/glob/util/strings"
)
type Any struct {
Separators []rune
}
func NewAny(s []rune) Any {
return Any{s}
}
func (self Any) Match(s string) bool {
return strings.IndexAnyRunes(s, self.Separators) == -1
}
func (self Any) Index(s string) (int, []int) {
found := strings.IndexAnyRunes(s, self.Separators)
switch found {
case -1:
case 0:
return 0, segments0
default:
s = s[:found]
}
segments := acquireSegments(len(s))
for i := range s {
segments = append(segments, i)
}
segments = append(segments, len(s))
return 0, segments
}
func (self Any) Len() int {
return lenNo
}
func (self Any) String() string {
return fmt.Sprintf("<any:![%s]>", string(self.Separators))
}

View File

@ -1,82 +0,0 @@
package match
import "fmt"
type AnyOf struct {
Matchers Matchers
}
func NewAnyOf(m ...Matcher) AnyOf {
return AnyOf{Matchers(m)}
}
func (self *AnyOf) Add(m Matcher) error {
self.Matchers = append(self.Matchers, m)
return nil
}
func (self AnyOf) Match(s string) bool {
for _, m := range self.Matchers {
if m.Match(s) {
return true
}
}
return false
}
func (self AnyOf) Index(s string) (int, []int) {
index := -1
segments := acquireSegments(len(s))
for _, m := range self.Matchers {
idx, seg := m.Index(s)
if idx == -1 {
continue
}
if index == -1 || idx < index {
index = idx
segments = append(segments[:0], seg...)
continue
}
if idx > index {
continue
}
// here idx == index
segments = appendMerge(segments, seg)
}
if index == -1 {
releaseSegments(segments)
return -1, nil
}
return index, segments
}
func (self AnyOf) Len() (l int) {
l = -1
for _, m := range self.Matchers {
ml := m.Len()
switch {
case l == -1:
l = ml
continue
case ml == -1:
return -1
case l != ml:
return -1
}
}
return
}
func (self AnyOf) String() string {
return fmt.Sprintf("<any_of:[%s]>", self.Matchers)
}

View File

@ -1,185 +0,0 @@
package match
import (
"fmt"
"unicode/utf8"
)
type BTree struct {
Value Matcher
Left Matcher
Right Matcher
ValueLengthRunes int
LeftLengthRunes int
RightLengthRunes int
LengthRunes int
}
func NewBTree(Value, Left, Right Matcher) (tree BTree) {
tree.Value = Value
tree.Left = Left
tree.Right = Right
lenOk := true
if tree.ValueLengthRunes = Value.Len(); tree.ValueLengthRunes == -1 {
lenOk = false
}
if Left != nil {
if tree.LeftLengthRunes = Left.Len(); tree.LeftLengthRunes == -1 {
lenOk = false
}
}
if Right != nil {
if tree.RightLengthRunes = Right.Len(); tree.RightLengthRunes == -1 {
lenOk = false
}
}
if lenOk {
tree.LengthRunes = tree.LeftLengthRunes + tree.ValueLengthRunes + tree.RightLengthRunes
} else {
tree.LengthRunes = -1
}
return tree
}
func (self BTree) Len() int {
return self.LengthRunes
}
// todo?
func (self BTree) Index(s string) (index int, segments []int) {
//inputLen := len(s)
//// try to cut unnecessary parts
//// by knowledge of length of right and left part
//offset, limit := self.offsetLimit(inputLen)
//for offset < limit {
// // search for matching part in substring
// vi, segments := self.Value.Index(s[offset:limit])
// if index == -1 {
// return -1, nil
// }
// if self.Left == nil {
// if index != offset {
// return -1, nil
// }
// } else {
// left := s[:offset+vi]
// i := self.Left.IndexSuffix(left)
// if i == -1 {
// return -1, nil
// }
// index = i
// }
// if self.Right != nil {
// for _, seg := range segments {
// right := s[:offset+vi+seg]
// }
// }
// l := s[:offset+index]
// var left bool
// if self.Left != nil {
// left = self.Left.Index(l)
// } else {
// left = l == ""
// }
//}
return -1, nil
}
func (self BTree) Match(s string) bool {
inputLen := len(s)
// try to cut unnecessary parts
// by knowledge of length of right and left part
offset, limit := self.offsetLimit(inputLen)
for offset < limit {
// search for matching part in substring
index, segments := self.Value.Index(s[offset:limit])
if index == -1 {
releaseSegments(segments)
return false
}
l := s[:offset+index]
var left bool
if self.Left != nil {
left = self.Left.Match(l)
} else {
left = l == ""
}
if left {
for i := len(segments) - 1; i >= 0; i-- {
length := segments[i]
var right bool
var r string
// if there is no string for the right branch
if inputLen <= offset+index+length {
r = ""
} else {
r = s[offset+index+length:]
}
if self.Right != nil {
right = self.Right.Match(r)
} else {
right = r == ""
}
if right {
releaseSegments(segments)
return true
}
}
}
_, step := utf8.DecodeRuneInString(s[offset+index:])
offset += index + step
releaseSegments(segments)
}
return false
}
func (self BTree) offsetLimit(inputLen int) (offset int, limit int) {
// self.Length, self.RLen and self.LLen are values meaning the length of runes for each part
// here we manipulating byte length for better optimizations
// but these checks still works, cause minLen of 1-rune string is 1 byte.
if self.LengthRunes != -1 && self.LengthRunes > inputLen {
return 0, 0
}
if self.LeftLengthRunes >= 0 {
offset = self.LeftLengthRunes
}
if self.RightLengthRunes >= 0 {
limit = inputLen - self.RightLengthRunes
} else {
limit = inputLen
}
return offset, limit
}
func (self BTree) String() string {
const n string = "<nil>"
var l, r string
if self.Left == nil {
l = n
} else {
l = self.Left.String()
}
if self.Right == nil {
r = n
} else {
r = self.Right.String()
}
return fmt.Sprintf("<btree:[%s<-%s->%s]>", l, self.Value, r)
}

View File

@ -1,58 +0,0 @@
package match
import (
"fmt"
"strings"
)
type Contains struct {
Needle string
Not bool
}
func NewContains(needle string, not bool) Contains {
return Contains{needle, not}
}
func (self Contains) Match(s string) bool {
return strings.Contains(s, self.Needle) != self.Not
}
func (self Contains) Index(s string) (int, []int) {
var offset int
idx := strings.Index(s, self.Needle)
if !self.Not {
if idx == -1 {
return -1, nil
}
offset = idx + len(self.Needle)
if len(s) <= offset {
return 0, []int{offset}
}
s = s[offset:]
} else if idx != -1 {
s = s[:idx]
}
segments := acquireSegments(len(s) + 1)
for i := range s {
segments = append(segments, offset+i)
}
return 0, append(segments, offset+len(s))
}
func (self Contains) Len() int {
return lenNo
}
func (self Contains) String() string {
var not string
if self.Not {
not = "!"
}
return fmt.Sprintf("<contains:%s[%s]>", not, self.Needle)
}

View File

@ -1,99 +0,0 @@
package match
import (
"fmt"
)
type EveryOf struct {
Matchers Matchers
}
func NewEveryOf(m ...Matcher) EveryOf {
return EveryOf{Matchers(m)}
}
func (self *EveryOf) Add(m Matcher) error {
self.Matchers = append(self.Matchers, m)
return nil
}
func (self EveryOf) Len() (l int) {
for _, m := range self.Matchers {
if ml := m.Len(); l > 0 {
l += ml
} else {
return -1
}
}
return
}
func (self EveryOf) Index(s string) (int, []int) {
var index int
var offset int
// make `in` with cap as len(s),
// cause it is the maximum size of output segments values
next := acquireSegments(len(s))
current := acquireSegments(len(s))
sub := s
for i, m := range self.Matchers {
idx, seg := m.Index(sub)
if idx == -1 {
releaseSegments(next)
releaseSegments(current)
return -1, nil
}
if i == 0 {
// we use copy here instead of `current = seg`
// cause seg is a slice from reusable buffer `in`
// and it could be overwritten in next iteration
current = append(current, seg...)
} else {
// clear the next
next = next[:0]
delta := index - (idx + offset)
for _, ex := range current {
for _, n := range seg {
if ex+delta == n {
next = append(next, n)
}
}
}
if len(next) == 0 {
releaseSegments(next)
releaseSegments(current)
return -1, nil
}
current = append(current[:0], next...)
}
index = idx + offset
sub = s[index:]
offset += idx
}
releaseSegments(next)
return index, current
}
func (self EveryOf) Match(s string) bool {
for _, m := range self.Matchers {
if !m.Match(s) {
return false
}
}
return true
}
func (self EveryOf) String() string {
return fmt.Sprintf("<every_of:[%s]>", self.Matchers)
}

View File

@ -1,49 +0,0 @@
package match
import (
"fmt"
"github.com/gobwas/glob/util/runes"
"unicode/utf8"
)
type List struct {
List []rune
Not bool
}
func NewList(list []rune, not bool) List {
return List{list, not}
}
func (self List) Match(s string) bool {
r, w := utf8.DecodeRuneInString(s)
if len(s) > w {
return false
}
inList := runes.IndexRune(self.List, r) != -1
return inList == !self.Not
}
func (self List) Len() int {
return lenOne
}
func (self List) Index(s string) (int, []int) {
for i, r := range s {
if self.Not == (runes.IndexRune(self.List, r) == -1) {
return i, segmentsByRuneLength[utf8.RuneLen(r)]
}
}
return -1, nil
}
func (self List) String() string {
var not string
if self.Not {
not = "!"
}
return fmt.Sprintf("<list:%s[%s]>", not, string(self.List))
}

View File

@ -1,81 +0,0 @@
package match
// todo common table of rune's length
import (
"fmt"
"strings"
)
const lenOne = 1
const lenZero = 0
const lenNo = -1
type Matcher interface {
Match(string) bool
Index(string) (int, []int)
Len() int
String() string
}
type Matchers []Matcher
func (m Matchers) String() string {
var s []string
for _, matcher := range m {
s = append(s, fmt.Sprint(matcher))
}
return fmt.Sprintf("%s", strings.Join(s, ","))
}
// appendMerge merges and sorts given already SORTED and UNIQUE segments.
func appendMerge(target, sub []int) []int {
lt, ls := len(target), len(sub)
out := make([]int, 0, lt+ls)
for x, y := 0, 0; x < lt || y < ls; {
if x >= lt {
out = append(out, sub[y:]...)
break
}
if y >= ls {
out = append(out, target[x:]...)
break
}
xValue := target[x]
yValue := sub[y]
switch {
case xValue == yValue:
out = append(out, xValue)
x++
y++
case xValue < yValue:
out = append(out, xValue)
x++
case yValue < xValue:
out = append(out, yValue)
y++
}
}
target = append(target[:0], out...)
return target
}
func reverseSegments(input []int) {
l := len(input)
m := l / 2
for i := 0; i < m; i++ {
input[i], input[l-i-1] = input[l-i-1], input[i]
}
}

Some files were not shown because too many files have changed in this diff Show More