// 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 }
// 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 }
// 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 }
// 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 }