diff --git a/agent/PSProxy.Agent/PSProxy.Agent.cs b/agent/PSProxy.Agent/PSProxy.Agent.cs index 6cb7091..c209a1a 100644 --- a/agent/PSProxy.Agent/PSProxy.Agent.cs +++ b/agent/PSProxy.Agent/PSProxy.Agent.cs @@ -95,6 +95,9 @@ namespace PSProxy.Agent using (var rsa = new RSACryptoServiceProvider()) { rsa.ImportParameters(ParseRsaPublicKey(pubDer)); + // .NET Framework RSACryptoServiceProvider supports OAEP only as + // SHA-1 with the default empty OAEP label. The server accepts + // this format for PowerShell 5.1 agent compatibility. encSecret = rsa.Encrypt(secret, true); } string hello = "HELLO " + B64Url(encSecret) + " " + B64Url(nonce) + "\n"; diff --git a/cmd/psproxy-server/main.go b/cmd/psproxy-server/main.go index 79ef21e..c81c154 100644 --- a/cmd/psproxy-server/main.go +++ b/cmd/psproxy-server/main.go @@ -5,6 +5,7 @@ import ( "crypto/hmac" "crypto/rand" "crypto/rsa" + "crypto/sha1" "crypto/sha256" "crypto/tls" "crypto/x509" @@ -693,6 +694,25 @@ func handleAgent(conn net.Conn, br *bufio.Reader, server *TunnelServer) { server.ClearSession(a) } +func decryptSessionSecret(key *rsa.PrivateKey, encSecret []byte) ([]byte, error) { + label := []byte("PS-Proxy PSP1 session") + secret, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, key, encSecret, label) + if err == nil { + return secret, nil + } + + // RSACryptoServiceProvider.Encrypt(..., true), used by the .NET Framework + // PowerShell agent, means OAEP-SHA1 with the default empty OAEP label. Keep + // accepting that wire format so already-staged agents can enroll. + for _, legacyLabel := range [][]byte{nil, label} { + legacySecret, legacyErr := rsa.DecryptOAEP(sha1.New(), rand.Reader, key, encSecret, legacyLabel) + if legacyErr == nil { + return legacySecret, nil + } + } + return nil, err +} + func serverHandshake(conn net.Conn, br *bufio.Reader, key *rsa.PrivateKey) (*protocol.SecureCodec, error) { line, err := br.ReadString('\n') if err != nil { @@ -713,7 +733,7 @@ func serverHandshake(conn net.Conn, br *bufio.Reader, key *rsa.PrivateKey) (*pro if len(clientNonce) != 32 { return nil, errors.New("invalid client nonce") } - secret, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, key, encSecret, []byte("PS-Proxy PSP1 session")) + secret, err := decryptSessionSecret(key, encSecret) if err != nil { return nil, err } diff --git a/cmd/psproxy-server/main_test.go b/cmd/psproxy-server/main_test.go index f03a38c..fee9c52 100644 --- a/cmd/psproxy-server/main_test.go +++ b/cmd/psproxy-server/main_test.go @@ -5,6 +5,7 @@ import ( "crypto/hmac" "crypto/rand" "crypto/rsa" + "crypto/sha1" "crypto/sha256" "crypto/x509" "encoding/base64" @@ -131,6 +132,25 @@ func TestSingleListenerAcceptReturnsEOFAfterFirstConn(t *testing.T) { t.Fatal("second accept should return EOF") } } +func TestDecryptSessionSecretAcceptsAgentSHA1OAEP(t *testing.T) { + key, err := rsa.GenerateKey(rand.Reader, 3072) + if err != nil { + t.Fatal(err) + } + secret := []byte("0123456789abcdef0123456789abcdef") + enc, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, &key.PublicKey, secret, nil) + if err != nil { + t.Fatal(err) + } + got, err := decryptSessionSecret(key, enc) + if err != nil { + t.Fatal(err) + } + if string(got) != string(secret) { + t.Fatalf("got %q want %q", got, secret) + } +} + func TestServerSecureHandshakeEncryptedFrame(t *testing.T) { key, err := rsa.GenerateKey(rand.Reader, 3072) if err != nil {