mirror of
https://github.com/mmatczuk/go-http-tunnel.git
synced 2026-05-15 14:16:17 -06:00
server: registry
This commit is contained in:
parent
c63ae8f383
commit
894f217a30
21 changed files with 886 additions and 309 deletions
190
id/id.go
Normal file
190
id/id.go
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
package id
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/base32"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/calmh/luhn"
|
||||
)
|
||||
|
||||
// ID is the type representing a generated ID.
|
||||
type ID [32]byte
|
||||
|
||||
// New generates a new ID from the given input bytes.
|
||||
func New(data []byte) ID {
|
||||
var id ID
|
||||
|
||||
hasher := sha256.New()
|
||||
hasher.Write(data)
|
||||
hasher.Sum(id[:0])
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
// NewFromString creates a new ID from the given string.
|
||||
func NewFromString(s string) ID {
|
||||
return New([]byte(s))
|
||||
}
|
||||
|
||||
// NewFromBytes creates a new ID with the value of the given byte slice. The
|
||||
// given byte slice must be 32 bytes long (the same length as ID), or this
|
||||
// function will panic.
|
||||
func NewFromBytes(b []byte) ID {
|
||||
var id ID
|
||||
|
||||
if len(b) != len(id) {
|
||||
panic("invalid slice length for id")
|
||||
}
|
||||
|
||||
copy(id[0:], b)
|
||||
return id
|
||||
}
|
||||
|
||||
// String returns the canonical representation of the ID.
|
||||
func (i ID) String() string {
|
||||
ss := base32.StdEncoding.EncodeToString(i[:])
|
||||
ss = strings.Trim(ss, "=")
|
||||
|
||||
// Add a Luhn check 'digit' for the ID.
|
||||
ss, err := luhnify(ss)
|
||||
if err != nil {
|
||||
// Should never happen
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Return the given ID as chunks.
|
||||
ss = chunkify(ss)
|
||||
|
||||
return ss
|
||||
}
|
||||
|
||||
// Compares the two given IDs. Note that this function is NOT SAFE AGAINST
|
||||
// TIMING ATTACKS. If you are simply checking for equality, please use the
|
||||
// Equals function, which is.
|
||||
func (i ID) Compare(other ID) int {
|
||||
return bytes.Compare(i[:], other[:])
|
||||
}
|
||||
|
||||
// Checks the two given IDs for equality. This function uses a constant-time
|
||||
// comparison algorithm to prevent timing attacks.
|
||||
func (i ID) Equals(other ID) bool {
|
||||
return subtle.ConstantTimeCompare(i[:], other[:]) == 1
|
||||
}
|
||||
|
||||
// Implements the `TextMarshaler` interface from the encoding package.
|
||||
func (i *ID) MarshalText() ([]byte, error) {
|
||||
return []byte(i.String()), nil
|
||||
}
|
||||
|
||||
// Implements the `TextUnmarshaler` interface from the encoding package.
|
||||
func (i *ID) UnmarshalText(bs []byte) (err error) {
|
||||
// Convert to the canonical encoding - uppercase, no '=', no chunks, and
|
||||
// with any potential typos fixed.
|
||||
id := string(bs)
|
||||
id = strings.Trim(id, "=")
|
||||
id = strings.ToUpper(id)
|
||||
id = untypeoify(id)
|
||||
id = unchunkify(id)
|
||||
|
||||
if len(id) != 56 {
|
||||
return errors.New("device ID invalid: incorrect length")
|
||||
}
|
||||
|
||||
// Remove & verify Luhn check digits
|
||||
id, err = unluhnify(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Base32 decode
|
||||
dec, err := base32.StdEncoding.DecodeString(id + "====")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Done!
|
||||
copy(i[:], dec)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add Luhn check digits to a string, returning the new one.
|
||||
func luhnify(s string) (string, error) {
|
||||
if len(s) != 52 {
|
||||
panic("unsupported string length")
|
||||
}
|
||||
|
||||
// Split the string into chunks of length 13, and add a Luhn check digit to
|
||||
// each one.
|
||||
res := make([]string, 0, 4)
|
||||
for i := 0; i < 4; i++ {
|
||||
chunk := s[i*13 : (i+1)*13]
|
||||
|
||||
l, err := luhn.Base32.Generate(chunk)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
res = append(res, fmt.Sprintf("%s%c", chunk, l))
|
||||
}
|
||||
|
||||
return res[0] + res[1] + res[2] + res[3], nil
|
||||
}
|
||||
|
||||
// Remove Luhn check digits from the given string, validating that they are
|
||||
// correct.
|
||||
func unluhnify(s string) (string, error) {
|
||||
if len(s) != 56 {
|
||||
return "", fmt.Errorf("unsupported string length %d", len(s))
|
||||
}
|
||||
|
||||
res := make([]string, 0, 4)
|
||||
for i := 0; i < 4; i++ {
|
||||
// 13 characters, plus the Luhn digit.
|
||||
chunk := s[i*14 : (i+1)*14]
|
||||
|
||||
// Get the expected check digit.
|
||||
l, err := luhn.Base32.Generate(chunk[0:13])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Validate the digits match.
|
||||
if fmt.Sprintf("%c", l) != chunk[13:] {
|
||||
return "", errors.New("check digit incorrect")
|
||||
}
|
||||
|
||||
res = append(res, chunk[0:13])
|
||||
}
|
||||
|
||||
return res[0] + res[1] + res[2] + res[3], nil
|
||||
}
|
||||
|
||||
// Returns a string split into chunks of size 7.
|
||||
func chunkify(s string) string {
|
||||
s = regexp.MustCompile("(.{7})").ReplaceAllString(s, "$1-")
|
||||
s = strings.Trim(s, "-")
|
||||
return s
|
||||
}
|
||||
|
||||
// Un-chunks a string by removing all hyphens and spaces.
|
||||
func unchunkify(s string) string {
|
||||
s = strings.Replace(s, "-", "", -1)
|
||||
s = strings.Replace(s, " ", "", -1)
|
||||
return s
|
||||
}
|
||||
|
||||
// We use base32 encoding, which uses 26 characters, and then the numbers
|
||||
// 234567. This is useful since the alphabet doesn't contain the numbers 0, 1,
|
||||
// or 8, which means we can replace them with their letter-lookalikes.
|
||||
func untypeoify(s string) string {
|
||||
s = strings.Replace(s, "0", "O", -1)
|
||||
s = strings.Replace(s, "1", "I", -1)
|
||||
s = strings.Replace(s, "8", "B", -1)
|
||||
return s
|
||||
}
|
||||
43
id/ptls.go
Normal file
43
id/ptls.go
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
package id
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var emptyID [32]byte
|
||||
|
||||
// PeerID is modified https://github.com/andrew-d/ptls/blob/b89c7dcc94630a77f225a48befd3710144c7c10e/ptls.go#L81
|
||||
func PeerID(conn *tls.Conn) (ID, error) {
|
||||
// Try a TLS connection over the given connection. We explicitly perform
|
||||
// the handshake, since we want to maintain the invariant that, if this
|
||||
// function returns successfully, then the connection should be valid
|
||||
// and verified.
|
||||
if err := conn.Handshake(); err != nil {
|
||||
return emptyID, err
|
||||
}
|
||||
|
||||
cs := conn.ConnectionState()
|
||||
|
||||
// We should have exactly one peer certificate.
|
||||
certs := cs.PeerCertificates
|
||||
if cl := len(certs); cl != 1 {
|
||||
return emptyID, ImproperCertsNumberError{cl}
|
||||
}
|
||||
|
||||
// Get remote cert's ID.
|
||||
remoteCert := certs[0]
|
||||
remoteID := New(remoteCert.Raw)
|
||||
|
||||
return remoteID, nil
|
||||
}
|
||||
|
||||
// ImproperCertsNumberError is returned from Server/Client whenever the remote
|
||||
// peer presents a number of PeerCertificates that is not 1.
|
||||
type ImproperCertsNumberError struct {
|
||||
n int
|
||||
}
|
||||
|
||||
func (e ImproperCertsNumberError) Error() string {
|
||||
return fmt.Sprintf("ptls: expecting 1 peer certificate, got %d", e.n)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue