// From the original patch to sshd.c:
// https://bitbucket.org/psiphon/psiphon-circumvention-system/commits/f40865ce624b680be840dc2432283c8137bd896d
func makeServerIdentificationLinePadding() ([]byte, error) {
	paddingLength, err := common.MakeSecureRandomInt(OBFUSCATE_MAX_PADDING - 2) // 2 = CRLF
	if err != nil {
		return nil, common.ContextError(err)
	}
	paddingLength += 2
	padding := make([]byte, paddingLength)

	// For backwards compatibility with some clients, send no more than 512 characters
	// per line (including CRLF). To keep the padding distribution between 0 and OBFUSCATE_MAX_PADDING
	// characters, we send lines that add up to padding_length characters including all CRLFs.

	minLineLength := 2
	maxLineLength := 512
	lineStartIndex := 0
	for paddingLength > 0 {
		lineLength := paddingLength
		if lineLength > maxLineLength {
			lineLength = maxLineLength
		}
		// Leave enough padding allowance to send a full CRLF on the last line
		if paddingLength-lineLength > 0 &&
			paddingLength-lineLength < minLineLength {
			lineLength -= minLineLength - (paddingLength - lineLength)
		}
		padding[lineStartIndex+lineLength-2] = '\r'
		padding[lineStartIndex+lineLength-1] = '\n'
		lineStartIndex += lineLength
		paddingLength -= lineLength
	}

	return padding, nil
}
Example #2
0
// selectProtocol is a helper that picks the tunnel protocol
func selectProtocol(
	config *Config, serverEntry *protocol.ServerEntry) (selectedProtocol string, err error) {

	// TODO: properly handle protocols (e.g. FRONTED-MEEK-OSSH) vs. capabilities (e.g., {FRONTED-MEEK, OSSH})
	// for now, the code is simply assuming that MEEK capabilities imply OSSH capability.
	if config.TunnelProtocol != "" {
		if !serverEntry.SupportsProtocol(config.TunnelProtocol) {
			return "", common.ContextError(fmt.Errorf("server does not have required capability"))
		}
		selectedProtocol = config.TunnelProtocol
	} else {
		// Pick at random from the supported protocols. This ensures that we'll eventually
		// try all possible protocols. Depending on network configuration, it may be the
		// case that some protocol is only available through multi-capability servers,
		// and a simpler ranked preference of protocols could lead to that protocol never
		// being selected.

		candidateProtocols := serverEntry.GetSupportedProtocols()
		if len(candidateProtocols) == 0 {
			return "", common.ContextError(fmt.Errorf("server does not have any supported capabilities"))
		}

		index, err := common.MakeSecureRandomInt(len(candidateProtocols))
		if err != nil {
			return "", common.ContextError(err)
		}
		selectedProtocol = candidateProtocols[index]
	}
	return selectedProtocol, nil
}
func makeSeedMessage(maxPadding int, seed []byte, clientToServerCipher *rc4.Cipher) ([]byte, error) {
	// paddingLength is integer in range [0, maxPadding]
	paddingLength, err := common.MakeSecureRandomInt(maxPadding + 1)
	if err != nil {
		return nil, common.ContextError(err)
	}
	padding, err := common.MakeSecureRandomBytes(paddingLength)
	if err != nil {
		return nil, common.ContextError(err)
	}
	buffer := new(bytes.Buffer)
	err = binary.Write(buffer, binary.BigEndian, seed)
	if err != nil {
		return nil, common.ContextError(err)
	}
	err = binary.Write(buffer, binary.BigEndian, uint32(OBFUSCATE_MAGIC_VALUE))
	if err != nil {
		return nil, common.ContextError(err)
	}
	err = binary.Write(buffer, binary.BigEndian, uint32(paddingLength))
	if err != nil {
		return nil, common.ContextError(err)
	}
	err = binary.Write(buffer, binary.BigEndian, padding)
	if err != nil {
		return nil, common.ContextError(err)
	}
	seedMessage := buffer.Bytes()
	clientToServerCipher.XORKeyStream(seedMessage[len(seed):], seedMessage[len(seed):])
	return seedMessage, nil
}
Example #4
0
// selectFrontingParameters is a helper which selects/generates meek fronting
// parameters where the server entry provides multiple options or patterns.
func selectFrontingParameters(
	serverEntry *protocol.ServerEntry) (frontingAddress, frontingHost string, err error) {

	if len(serverEntry.MeekFrontingAddressesRegex) > 0 {

		// Generate a front address based on the regex.

		frontingAddress, err = regen.Generate(serverEntry.MeekFrontingAddressesRegex)
		if err != nil {
			return "", "", common.ContextError(err)
		}
	} else {

		// Randomly select, for this connection attempt, one front address for
		// fronting-capable servers.

		if len(serverEntry.MeekFrontingAddresses) == 0 {
			return "", "", common.ContextError(errors.New("MeekFrontingAddresses is empty"))
		}
		index, err := common.MakeSecureRandomInt(len(serverEntry.MeekFrontingAddresses))
		if err != nil {
			return "", "", common.ContextError(err)
		}
		frontingAddress = serverEntry.MeekFrontingAddresses[index]
	}

	if len(serverEntry.MeekFrontingHosts) > 0 {
		index, err := common.MakeSecureRandomInt(len(serverEntry.MeekFrontingHosts))
		if err != nil {
			return "", "", common.ContextError(err)
		}
		frontingHost = serverEntry.MeekFrontingHosts[index]
	} else {
		// Backwards compatibility case
		frontingHost = serverEntry.MeekFrontingHost
	}

	return
}
Example #5
0
// makeMeekSessionID creates a new session ID. The variable size is intended to
// frustrate traffic analysis of both plaintext and TLS meek traffic.
func makeMeekSessionID() (string, error) {
	size := MEEK_MIN_SESSION_ID_LENGTH
	n, err := common.MakeSecureRandomInt(MEEK_MAX_SESSION_ID_LENGTH - MEEK_MIN_SESSION_ID_LENGTH)
	if err != nil {
		return "", common.ContextError(err)
	}
	size += n
	sessionID, err := common.MakeRandomStringBase64(size)
	if err != nil {
		return "", common.ContextError(err)
	}
	return sessionID, nil
}
func extractSshPackets(writeBuffer []byte) ([]byte, []byte, bool, error) {
	var packetBuffer, packetsBuffer []byte
	hasMsgNewKeys := false
	for len(writeBuffer) >= SSH_PACKET_PREFIX_LENGTH {
		packetLength, paddingLength, payloadLength, messageLength := getSshPacketPrefix(writeBuffer)
		if len(writeBuffer) < messageLength {
			// We don't have the complete packet yet
			break
		}
		packetBuffer = append([]byte(nil), writeBuffer[:messageLength]...)
		writeBuffer = writeBuffer[messageLength:]
		if payloadLength > 0 {
			packetType := int(packetBuffer[SSH_PACKET_PREFIX_LENGTH])
			if packetType == SSH_MSG_NEWKEYS {
				hasMsgNewKeys = true
			}
		}
		// Padding transformation
		// See RFC 4253 sec. 6 for constraints
		possiblePaddings := (SSH_MAX_PADDING_LENGTH - paddingLength) / SSH_PADDING_MULTIPLE
		if possiblePaddings > 0 {
			// selectedPadding is integer in range [0, possiblePaddings)
			selectedPadding, err := common.MakeSecureRandomInt(possiblePaddings)
			if err != nil {
				return nil, nil, false, common.ContextError(err)
			}
			extraPaddingLength := selectedPadding * SSH_PADDING_MULTIPLE
			extraPadding, err := common.MakeSecureRandomBytes(extraPaddingLength)
			if err != nil {
				return nil, nil, false, common.ContextError(err)
			}
			setSshPacketPrefix(
				packetBuffer, packetLength+extraPaddingLength, paddingLength+extraPaddingLength)
			packetBuffer = append(packetBuffer, extraPadding...)
		}
		packetsBuffer = append(packetsBuffer, packetBuffer...)
	}
	return writeBuffer, packetsBuffer, hasMsgNewKeys, nil
}
// makeCookie creates the cookie to be sent with initial meek HTTP request.
// The purpose of the cookie is to send the following to the server:
//   ServerAddress -- the Psiphon Server address the meek server should relay to
//   SessionID -- the Psiphon session ID (used by meek server to relay geolocation
//     information obtained from the CDN through to the Psiphon Server)
//   MeekProtocolVersion -- tells the meek server that this client understands
//     the latest protocol.
// The server will create a session using these values and send the session ID
// back to the client via Set-Cookie header. Client must use that value with
// all consequent HTTP requests
// In unfronted meek mode, the cookie is visible over the adversary network, so the
// cookie is encrypted and obfuscated.
func makeMeekCookie(meekConfig *MeekConfig) (cookie *http.Cookie, err error) {

	// Make the JSON data
	serverAddress := meekConfig.PsiphonServerAddress
	cookieData := &meekCookieData{
		ServerAddress:       serverAddress,
		SessionID:           meekConfig.SessionID,
		MeekProtocolVersion: MEEK_PROTOCOL_VERSION,
	}
	serializedCookie, err := json.Marshal(cookieData)
	if err != nil {
		return nil, common.ContextError(err)
	}

	// Encrypt the JSON data
	// NaCl box is used for encryption. The peer public key comes from the server entry.
	// Nonce is always all zeros, and is not sent in the cookie (the server also uses an all-zero nonce).
	// http://nacl.cace-project.eu/box.html:
	// "There is no harm in having the same nonce for different messages if the {sender, receiver} sets are
	// different. This is true even if the sets overlap. For example, a sender can use the same nonce for two
	// different messages if the messages are sent to two different public keys."
	var nonce [24]byte
	var publicKey [32]byte
	decodedPublicKey, err := base64.StdEncoding.DecodeString(meekConfig.MeekCookieEncryptionPublicKey)
	if err != nil {
		return nil, common.ContextError(err)
	}
	copy(publicKey[:], decodedPublicKey)
	ephemeralPublicKey, ephemeralPrivateKey, err := box.GenerateKey(rand.Reader)
	if err != nil {
		return nil, common.ContextError(err)
	}
	box := box.Seal(nil, serializedCookie, &nonce, &publicKey, ephemeralPrivateKey)
	encryptedCookie := make([]byte, 32+len(box))
	copy(encryptedCookie[0:32], ephemeralPublicKey[0:32])
	copy(encryptedCookie[32:], box)

	// Obfuscate the encrypted data
	obfuscator, err := common.NewClientObfuscator(
		&common.ObfuscatorConfig{Keyword: meekConfig.MeekObfuscatedKey, MaxPadding: MEEK_COOKIE_MAX_PADDING})
	if err != nil {
		return nil, common.ContextError(err)
	}
	obfuscatedCookie := obfuscator.SendSeedMessage()
	seedLen := len(obfuscatedCookie)
	obfuscatedCookie = append(obfuscatedCookie, encryptedCookie...)
	obfuscator.ObfuscateClientToServer(obfuscatedCookie[seedLen:])

	// Format the HTTP cookie
	// The format is <random letter 'A'-'Z'>=<base64 data>, which is intended to match common cookie formats.
	A := int('A')
	Z := int('Z')
	// letterIndex is integer in range [int('A'), int('Z')]
	letterIndex, err := common.MakeSecureRandomInt(Z - A + 1)
	if err != nil {
		return nil, common.ContextError(err)
	}
	return &http.Cookie{
			Name:  string(byte(A + letterIndex)),
			Value: base64.StdEncoding.EncodeToString(obfuscatedCookie)},
		nil
}
// GenerateWebServerCertificate creates a self-signed web server certificate,
// using the specified host name (commonName).
// This is primarily intended for use by MeekServer to generate on-the-fly,
// self-signed TLS certificates for fronted HTTPS mode. In this case, the nature
// of the certificate is non-circumvention; it only has to be acceptable to the
// front CDN making connections to meek.
// The same certificates are used for unfronted HTTPS meek. In this case, the
// certificates may be a fingerprint used to detect Psiphon servers or traffic.
// TODO: more effort to mitigate fingerprinting these certificates.
//
// In addition, GenerateWebServerCertificate is used by GenerateConfig to create
// Psiphon web server certificates for test/example configurations. If these Psiphon
// web server certificates are used in production, the same caveats about
// fingerprints apply.
func GenerateWebServerCertificate(commonName string) (string, string, error) {

	// Based on https://golang.org/src/crypto/tls/generate_cert.go
	// TODO: use other key types: anti-fingerprint by varying params

	rsaKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return "", "", common.ContextError(err)
	}

	// Validity period is ~10 years, starting some number of ~months
	// back in the last year.

	age, err := common.MakeSecureRandomInt(12)
	if err != nil {
		return "", "", common.ContextError(err)
	}
	age += 1
	validityPeriod := 10 * 365 * 24 * time.Hour
	notBefore := time.Now().Add(time.Duration(-age) * 30 * 24 * time.Hour).UTC()
	notAfter := notBefore.Add(validityPeriod).UTC()

	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
	if err != nil {
		return "", "", common.ContextError(err)
	}

	publicKeyBytes, err := x509.MarshalPKIXPublicKey(rsaKey.Public())
	if err != nil {
		return "", "", common.ContextError(err)
	}
	// as per RFC3280 sec. 4.2.1.2
	subjectKeyID := sha1.Sum(publicKeyBytes)

	var subject pkix.Name
	if commonName != "" {
		subject = pkix.Name{CommonName: commonName}
	}

	template := x509.Certificate{
		SerialNumber:          serialNumber,
		Subject:               subject,
		NotBefore:             notBefore,
		NotAfter:              notAfter,
		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		BasicConstraintsValid: true,
		IsCA:         true,
		SubjectKeyId: subjectKeyID[:],
		MaxPathLen:   1,
		Version:      2,
	}

	derCert, err := x509.CreateCertificate(
		rand.Reader,
		&template,
		&template,
		rsaKey.Public(),
		rsaKey)
	if err != nil {
		return "", "", common.ContextError(err)
	}

	webServerCertificate := pem.EncodeToMemory(
		&pem.Block{
			Type:  "CERTIFICATE",
			Bytes: derCert,
		},
	)

	webServerPrivateKey := pem.EncodeToMemory(
		&pem.Block{
			Type:  "RSA PRIVATE KEY",
			Bytes: x509.MarshalPKCS1PrivateKey(rsaKey),
		},
	)

	return string(webServerCertificate), string(webServerPrivateKey), nil
}
// tcpDial is the platform-specific part of interruptibleTCPDial
//
// To implement socket device binding, the lower-level syscall APIs are used.
// The sequence of syscalls in this implementation are taken from:
// https://code.google.com/p/go/issues/detail?id=6966
func tcpDial(addr string, config *DialConfig, dialResult chan error) (net.Conn, error) {

	// Like interruption, this timeout doesn't stop this connection goroutine,
	// it just unblocks the calling interruptibleTCPDial.
	if config.ConnectTimeout != 0 {
		time.AfterFunc(config.ConnectTimeout, func() {
			select {
			case dialResult <- errors.New("connect timeout"):
			default:
			}
		})
	}

	// Get the remote IP and port, resolving a domain name if necessary
	host, strPort, err := net.SplitHostPort(addr)
	if err != nil {
		return nil, common.ContextError(err)
	}
	port, err := strconv.Atoi(strPort)
	if err != nil {
		return nil, common.ContextError(err)
	}
	ipAddrs, err := LookupIP(host, config)
	if err != nil {
		return nil, common.ContextError(err)
	}
	if len(ipAddrs) < 1 {
		return nil, common.ContextError(errors.New("no IP address"))
	}

	// Select an IP at random from the list, so we're not always
	// trying the same IP (when > 1) which may be blocked.
	// TODO: retry all IPs until one connects? For now, this retry
	// will happen on subsequent TCPDial calls, when a different IP
	// is selected.
	index, err := common.MakeSecureRandomInt(len(ipAddrs))
	if err != nil {
		return nil, common.ContextError(err)
	}

	// TODO: IPv6 support
	var ip [4]byte
	copy(ip[:], ipAddrs[index].To4())

	// Create a socket and bind to device, when configured to do so
	socketFd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, 0)
	if err != nil {
		return nil, common.ContextError(err)
	}

	if config.DeviceBinder != nil {
		// WARNING: this potentially violates the direction to not call into
		// external components after the Controller may have been stopped.
		// TODO: rework DeviceBinder as an internal 'service' which can trap
		// external calls when they should not be made?
		err = config.DeviceBinder.BindToDevice(socketFd)
		if err != nil {
			syscall.Close(socketFd)
			return nil, common.ContextError(fmt.Errorf("BindToDevice failed: %s", err))
		}
	}

	sockAddr := syscall.SockaddrInet4{Addr: ip, Port: port}
	err = syscall.Connect(socketFd, &sockAddr)
	if err != nil {
		syscall.Close(socketFd)
		return nil, common.ContextError(err)
	}

	// Convert the socket fd to a net.Conn
	file := os.NewFile(uintptr(socketFd), "")
	netConn, err := net.FileConn(file) // net.FileConn() dups socketFd
	file.Close()                       // file.Close() closes socketFd
	if err != nil {
		return nil, common.ContextError(err)
	}

	return netConn, nil
}