diff --git a/Net/Makefile b/Net/Makefile index 1cdc7b637..9ae684bf5 100644 --- a/Net/Makefile +++ b/Net/Makefile @@ -32,7 +32,8 @@ objects = \ RemoteSyslogChannel RemoteSyslogListener SMTPChannel \ WebSocket WebSocketImpl \ OAuth10Credentials OAuth20Credentials \ - PollSet UDPClient UDPServerParams + PollSet UDPClient UDPServerParams \ + NTLMCredentials HTTPNTLMCredentials target = PocoNet target_version = $(LIBVERSION) diff --git a/Net/include/Poco/Net/HTTPAuthenticationParams.h b/Net/include/Poco/Net/HTTPAuthenticationParams.h index 492e2c45d..ebfd7812a 100644 --- a/Net/include/Poco/Net/HTTPAuthenticationParams.h +++ b/Net/include/Poco/Net/HTTPAuthenticationParams.h @@ -33,6 +33,9 @@ class HTTPResponse; class Net_API HTTPAuthenticationParams: public NameValueCollection /// Collection of name-value pairs of HTTP authentication header (i.e. /// "realm", "qop", "nonce" in case of digest authentication header). + /// + /// For NTLM, the base64-encoded NTLM message is available from + /// the "NTLM" property. { public: HTTPAuthenticationParams(); @@ -89,6 +92,7 @@ public: /// request or response authentication header. static const std::string REALM; + static const std::string NTLM; static const std::string WWW_AUTHENTICATE; static const std::string PROXY_AUTHENTICATE; diff --git a/Net/include/Poco/Net/HTTPClientSession.h b/Net/include/Poco/Net/HTTPClientSession.h index b3a33b4e8..a42c9cb43 100644 --- a/Net/include/Poco/Net/HTTPClientSession.h +++ b/Net/include/Poco/Net/HTTPClientSession.h @@ -20,6 +20,9 @@ #include "Poco/Net/Net.h" #include "Poco/Net/HTTPSession.h" +#include "Poco/Net/HTTPBasicCredentials.h" +#include "Poco/Net/HTTPDigestCredentials.h" +#include "Poco/Net/HTTPNTLMCredentials.h" #include "Poco/Net/SocketAddress.h" #include "Poco/SharedPtr.h" #include @@ -62,11 +65,20 @@ class Net_API HTTPClientSession: public HTTPSession /// set up a session through a proxy. { public: + enum ProxyAuthentication + { + PROXY_AUTH_NONE, /// No proxy authentication + PROXY_AUTH_HTTP_BASIC, /// HTTP Basic proxy authentication (default, if username and password are supplied) + PROXY_AUTH_HTTP_DIGEST, /// HTTP Digest proxy authentication + PROXY_AUTH_NTLM /// NTLMv2 proxy authentication + }; + struct ProxyConfig /// HTTP proxy server configuration. { ProxyConfig(): - port(HTTP_PORT) + port(HTTP_PORT), + authMethod(PROXY_AUTH_HTTP_BASIC) { } @@ -82,6 +94,9 @@ public: /// A regular expression defining hosts for which the proxy should be bypassed, /// e.g. "localhost|127\.0\.0\.1|192\.168\.0\.\d+". Can also be an empty /// string to disable proxy bypassing. + + ProxyAuthentication authMethod; + /// The authentication method to use - HTTP Basic or NTLM. }; HTTPClientSession(); @@ -110,47 +125,47 @@ public: /// /// The host must not be changed once there is an /// open connection to the server. - + const std::string& getHost() const; /// Returns the host name of the target HTTP server. - + void setPort(Poco::UInt16 port); /// Sets the port number of the target HTTP server. /// /// The port number must not be changed once there is an /// open connection to the server. - + Poco::UInt16 getPort() const; /// Returns the port number of the target HTTP server. void setProxy(const std::string& host, Poco::UInt16 port = HTTPSession::HTTP_PORT); /// Sets the proxy host name and port number. - + void setProxyHost(const std::string& host); /// Sets the host name of the proxy server. - + void setProxyPort(Poco::UInt16 port); /// Sets the port number of the proxy server. - + const std::string& getProxyHost() const; /// Returns the proxy host name. - + Poco::UInt16 getProxyPort() const; /// Returns the proxy port number. - + void setProxyCredentials(const std::string& username, const std::string& password); /// Sets the username and password for proxy authentication. /// Only Basic authentication is supported. - + void setProxyUsername(const std::string& username); /// Sets the username for proxy authentication. /// Only Basic authentication is supported. const std::string& getProxyUsername() const; /// Returns the username for proxy authentication. - + void setProxyPassword(const std::string& password); - /// Sets the password for proxy authentication. + /// Sets the password for proxy authentication. /// Only Basic authentication is supported. const std::string& getProxyPassword() const; @@ -177,10 +192,10 @@ public: void setKeepAliveTimeout(const Poco::Timespan& timeout); /// Sets the connection timeout for HTTP connections. - + const Poco::Timespan& getKeepAliveTimeout() const; /// Returns the connection timeout for HTTP connections. - + virtual std::ostream& sendRequest(HTTPRequest& request); /// Sends the header for the given HTTP request to /// the server. @@ -200,9 +215,9 @@ public: /// be reused and persistent connections are enabled /// to ensure a new connection will be set up /// for the next request. - + virtual std::istream& receiveResponse(HTTPResponse& response); - /// Receives the header for the response to the previous + /// Receives the header for the response to the previous /// HTTP request. /// /// The returned input stream can be used to read @@ -213,7 +228,7 @@ public: /// It must be ensured that the response stream /// is fully consumed before sending a new request /// and persistent connections are enabled. Otherwise, - /// the unread part of the response body may be treated as + /// the unread part of the response body may be treated as /// part of the next request's response header, resulting /// in a Poco::Net::MessageException being thrown. /// @@ -224,17 +239,17 @@ public: /// be reused and persistent connections are enabled /// to ensure a new connection will be set up /// for the next request. - + virtual bool peekResponse(HTTPResponse& response); /// If the request contains a "Expect: 100-continue" header, - /// (see HTTPRequest::setExpectContinue()) this method can be - /// used to check whether the server has sent a 100 Continue response + /// (see HTTPRequest::setExpectContinue()) this method can be + /// used to check whether the server has sent a 100 Continue response /// before continuing with the request, i.e. sending the request body, /// after calling sendRequest(). /// /// Returns true if the server has responded with 100 Continue, /// otherwise false. The HTTPResponse object contains the - /// response sent by the server. + /// response sent by the server. /// /// In any case, receiveResponse() must be called afterwards as well in /// order to complete the request. The same HTTPResponse object @@ -254,11 +269,11 @@ public: /// or receiveResponse() throws an exception, or /// the request or response stream changes into /// fail or bad state, but not eof state). - + virtual bool secure() const; /// Return true iff the session uses SSL or TLS, /// or false otherwise. - + bool bypassProxy() const; /// Returns true if the proxy should be bypassed /// for the current host. @@ -268,32 +283,45 @@ protected: { DEFAULT_KEEP_ALIVE_TIMEOUT = 8 }; - + void reconnect(); /// Connects the underlying socket to the HTTP server. int write(const char* buffer, std::streamsize length); /// Tries to re-connect if keep-alive is on. - + + std::ostream& sendRequestImpl(const HTTPRequest& request); + /// Sends the given HTTPRequest over an existing connection. + virtual std::string proxyRequestPrefix() const; /// Returns the prefix prepended to the URI for proxy requests /// (e.g., "http://myhost.com"). virtual bool mustReconnect() const; /// Checks if we can reuse a persistent connection. - + virtual void proxyAuthenticate(HTTPRequest& request); /// Sets the proxy credentials (Proxy-Authorization header), if /// proxy username and password have been set. - void proxyAuthenticateImpl(HTTPRequest& request); + void proxyAuthenticateImpl(HTTPRequest& request, const ProxyConfig& proxyConfig); /// Sets the proxy credentials (Proxy-Authorization header), if /// proxy username and password have been set. - + + void proxyAuthenticateDigest(HTTPRequest& request); + /// Initiates a HTTP Digest authentication handshake with the proxy. + + void proxyAuthenticateNTLM(HTTPRequest& request); + /// Initiates a HTTP NTLM authentication handshake with the proxy. + + void sendChallengeRequest(const HTTPRequest& request, HTTPResponse& response); + /// Sends a probe request for Digest and NTLM authentication + /// to obtain the server challenge. + StreamSocket proxyConnect(); /// Sends a CONNECT request to the proxy server and returns /// a StreamSocket for the resulting connection. - + void proxyTunnel(); /// Calls proxyConnect() and attaches the resulting StreamSocket /// to the HTTPClientSession. @@ -310,9 +338,13 @@ private: bool _responseReceived; Poco::SharedPtr _pRequestStream; Poco::SharedPtr _pResponseStream; + HTTPBasicCredentials _proxyBasicCreds; + HTTPDigestCredentials _proxyDigestCreds; + HTTPNTLMCredentials _proxyNTLMCreds; + bool _ntlmProxyAuthenticated; static ProxyConfig _globalProxyConfig; - + HTTPClientSession(const HTTPClientSession&); HTTPClientSession& operator = (const HTTPClientSession&); diff --git a/Net/include/Poco/Net/HTTPCredentials.h b/Net/include/Poco/Net/HTTPCredentials.h index c0ccccc03..608f426c8 100644 --- a/Net/include/Poco/Net/HTTPCredentials.h +++ b/Net/include/Poco/Net/HTTPCredentials.h @@ -20,6 +20,7 @@ #include "Poco/Net/HTTPDigestCredentials.h" +#include "Poco/Net/HTTPNTLMCredentials.h" namespace Poco { @@ -37,13 +38,13 @@ class HTTPResponse; class Net_API HTTPCredentials /// This is a utility class for working with HTTP - /// authentication (basic or digest) in HTTPRequest objects. + /// authentication (Basic, Digest or NTLM) in HTTPRequest objects. /// /// Usage is as follows: /// First, create a HTTPCredentials object containing /// the username and password. /// Poco::Net::HTTPCredentials creds("user", "s3cr3t"); - /// + /// /// Second, send the HTTP request with Poco::Net::HTTPClientSession. /// Poco::Net::HTTPClientSession session("pocoproject.org"); /// Poco::Net::HTTPRequest request(HTTPRequest::HTTP_GET, "/index.html", HTTPMessage::HTTP_1_1); @@ -51,7 +52,7 @@ class Net_API HTTPCredentials /// Poco::Net::HTTPResponse; /// std::istream& istr = session.receiveResponse(response); /// - /// If the server responds with a 401 status, authenticate the + /// If the server responds with a 401 status, authenticate the /// request and resend it: /// if (response.getStatus() == Poco::Net::HTTPResponse::HTTP_UNAUTHORIZED) /// { @@ -61,7 +62,7 @@ class Net_API HTTPCredentials /// } /// /// To perform multiple authenticated requests, call updateAuthInfo() - /// instead of authenticate() on subsequent requests. + /// instead of authenticate() on subsequent requests. /// creds.updateAuthInfo(request); /// session.sendRequest(request); /// ... @@ -132,17 +133,26 @@ public: static bool isDigestCredentials(const std::string& header); /// Returns true if authentication header is for Digest authentication. + static bool isNTLMCredentials(const std::string& header); + /// Returns true if authentication header is for NTLM authentication. + static bool hasBasicCredentials(const HTTPRequest& request); - /// Returns true if Authorization with Basic credentials header is present in the request. + /// Returns true if an Authorization header with Basic credentials is present in the request. static bool hasDigestCredentials(const HTTPRequest& request); - /// Returns true if Authorization with Digest credentials header is present in the request. + /// Returns true if an Authorization header with Digest credentials is present in the request. + + static bool hasNTLMCredentials(const HTTPRequest& request); + /// Returns true if an Authorization header with NTLM credentials is present in the request. static bool hasProxyBasicCredentials(const HTTPRequest& request); - /// Returns true if Authorization with Basic credentials header is present in the request. + /// Returns true if a Proxy-Authorization header with Basic credentials is present in the request. static bool hasProxyDigestCredentials(const HTTPRequest& request); - /// Returns true if Authorization with Digest credentials header is present in the request. + /// Returns true if a Proxy-Authorization header with Digest credentials is present in the request. + + static bool hasProxyNTLMCredentials(const HTTPRequest& request); + /// Returns true if a Proxy-Authorization header with Digest credentials is present in the request. static void extractCredentials(const std::string& userInfo, std::string& username, std::string& password); /// Extracts username and password from user:password information string. @@ -155,6 +165,7 @@ private: HTTPCredentials& operator = (const HTTPCredentials&); HTTPDigestCredentials _digest; + HTTPNTLMCredentials _ntlm; }; @@ -172,7 +183,7 @@ inline const std::string& HTTPCredentials::getUsername() const return _digest.getUsername(); } - + inline void HTTPCredentials::setPassword(const std::string& password) { _digest.setPassword(password); diff --git a/Net/include/Poco/Net/HTTPMessage.h b/Net/include/Poco/Net/HTTPMessage.h index 345543d5a..98d9dd60b 100644 --- a/Net/include/Poco/Net/HTTPMessage.h +++ b/Net/include/Poco/Net/HTTPMessage.h @@ -39,22 +39,22 @@ class Net_API HTTPMessage: public MessageHeader public: void setVersion(const std::string& version); /// Sets the HTTP version for this message. - + const std::string& getVersion() const; /// Returns the HTTP version for this message. - + void setContentLength(std::streamsize length); /// Sets the Content-Length header. /// /// If length is UNKNOWN_CONTENT_LENGTH, removes /// the Content-Length header. - + std::streamsize getContentLength() const; /// Returns the content length for this message, /// which may be UNKNOWN_CONTENT_LENGTH if /// no Content-Length header is present. -#if defined(POCO_HAVE_INT64) +#if defined(POCO_HAVE_INT64) void setContentLength64(Poco::Int64 length); /// Sets the Content-Length header. /// @@ -89,30 +89,30 @@ public: /// Normally, this is the value of the Transfer-Encoding /// header field. If no such field is present, /// returns IDENTITY_TRANSFER_CODING. - + void setChunkedTransferEncoding(bool flag); /// If flag is true, sets the Transfer-Encoding header to /// chunked. Otherwise, removes the Transfer-Encoding /// header. - + bool getChunkedTransferEncoding() const; /// Returns true if the Transfer-Encoding header is set /// and its value is chunked. - + void setContentType(const std::string& mediaType); /// Sets the content type for this message. /// /// Specify NO_CONTENT_TYPE to remove the /// Content-Type header. - - void setContentType(const MediaType& mediaType); + + void setContentType(const MediaType& mediaType); /// Sets the content type for this message. - + const std::string& getContentType() const; /// Returns the content type for this message. /// - /// If no Content-Type header is present, - /// returns UNKNOWN_CONTENT_TYPE. + /// If no Content-Type header is present, + /// returns UNKNOWN_CONTENT_TYPE. void setKeepAlive(bool keepAlive); /// Sets the value of the Connection header field. @@ -134,12 +134,13 @@ public: static const int UNKNOWN_CONTENT_LENGTH; static const std::string UNKNOWN_CONTENT_TYPE; - + static const std::string CONTENT_LENGTH; static const std::string CONTENT_TYPE; static const std::string TRANSFER_ENCODING; static const std::string CONNECTION; - + static const std::string PROXY_CONNECTION; + static const std::string CONNECTION_KEEP_ALIVE; static const std::string CONNECTION_CLOSE; @@ -153,13 +154,16 @@ protected: /// Creates the HTTPMessage and sets /// the version. + HTTPMessage(const HTTPMessage& other); + /// Copy constructor. + + HTTPMessage& operator = (const HTTPMessage& other); + /// Assignment operator. + virtual ~HTTPMessage(); /// Destroys the HTTPMessage. - + private: - HTTPMessage(const HTTPMessage&); - HTTPMessage& operator = (const HTTPMessage&); - std::string _version; }; diff --git a/Net/include/Poco/Net/HTTPNTLMCredentials.h b/Net/include/Poco/Net/HTTPNTLMCredentials.h new file mode 100644 index 000000000..1c181879c --- /dev/null +++ b/Net/include/Poco/Net/HTTPNTLMCredentials.h @@ -0,0 +1,128 @@ +// +// HTTPNTLMCredentials.h +// +// Library: Net +// Package: HTTP +// Module: HTTPNTLMCredentials +// +// Definition of the HTTPNTLMCredentials class. +// +// Copyright (c) 2019, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#ifndef Net_HTTPNTLMCredentials_INCLUDED +#define Net_HTTPNTLMCredentials_INCLUDED + + +#include "Poco/Net/Net.h" + + +namespace Poco { +namespace Net { + + +class HTTPRequest; +class HTTPResponse; + + +class Net_API HTTPNTLMCredentials + /// This is a utility class for working with + /// HTTP NTLMv2 Authentication in HTTPRequest + /// objects. +{ +public: + HTTPNTLMCredentials(); + /// Creates an empty HTTPNTLMCredentials object. + + HTTPNTLMCredentials(const std::string& username, const std::string& password); + /// Creates a HTTPNTLMCredentials object with the given username and password. + + ~HTTPNTLMCredentials(); + /// Destroys the HTTPNTLMCredentials. + + void reset(); + /// Resets the HTTPNTLMCredentials object to a clean state. + + void setUsername(const std::string& username); + /// Sets the username. + + const std::string& getUsername() const; + /// Returns the username. + + void setPassword(const std::string& password); + /// Sets the password. + + const std::string& getPassword() const; + /// Returns the password. + + void authenticate(HTTPRequest& request, const HTTPResponse& response); + /// Parses WWW-Authenticate header of the HTTPResponse, initializes + /// internal state, and adds authentication information to the given HTTPRequest. + + void authenticate(HTTPRequest& request, const std::string& ntlmChallengeBase64); + /// Initializes internal state according to information from the + /// ntlmChallengeBase64, and adds authentication + /// information to the given HTTPRequest. + /// + /// Throws InvalidArgumentException if responseAuthParams is + /// invalid. + + void updateAuthInfo(HTTPRequest& request); + /// Updates internal state and adds authentication information to + /// the given HTTPRequest. + + void proxyAuthenticate(HTTPRequest& request, const HTTPResponse& response); + /// Parses Proxy-Authenticate header of the HTTPResponse, initializes + /// internal state, and adds proxy authentication information to the given HTTPRequest. + + void proxyAuthenticate(HTTPRequest& request, const std::string& ntlmChallengeBase64); + /// Initializes internal state according to information from the + /// HTTPAuthenticationParams of the response, and adds proxy authentication + /// information to the given HTTPRequest. + /// + /// Throws InvalidArgumentException if HTTPAuthenticationParams is + /// invalid or some required parameter is missing. + /// Throws NotImplementedException in case of unsupported digest + /// algorithm or quality of protection method. + + void updateProxyAuthInfo(HTTPRequest& request); + /// Updates internal state and adds proxy authentication information to + /// the given HTTPRequest. + + static const std::string SCHEME; + +private: + HTTPNTLMCredentials(const HTTPNTLMCredentials&); + HTTPNTLMCredentials& operator = (const HTTPNTLMCredentials&); + + std::string createNTLMMessage(const std::string& ntlmChallengeBase64); + static void splitUsername(const std::string& usernameAndDomain, std::string& username, std::string& domain); + + std::string _username; + std::string _password; +}; + + +// +// inlines +// +inline const std::string& HTTPNTLMCredentials::getUsername() const +{ + return _username; +} + + +inline const std::string& HTTPNTLMCredentials::getPassword() const +{ + return _password; +} + + +} } // namespace Poco::Net + + +#endif // Net_HTTPNTLMCredentials_INCLUDED diff --git a/Net/include/Poco/Net/HTTPRequest.h b/Net/include/Poco/Net/HTTPRequest.h index 817a74dc4..bbf2a8b9e 100644 --- a/Net/include/Poco/Net/HTTPRequest.h +++ b/Net/include/Poco/Net/HTTPRequest.h @@ -38,20 +38,26 @@ class Net_API HTTPRequest: public HTTPMessage public: HTTPRequest(); /// Creates a GET / HTTP/1.0 HTTP request. - - HTTPRequest(const std::string& version); + + explicit HTTPRequest(const std::string& version); /// Creates a GET / HTTP/1.x request with /// the given version (HTTP/1.0 or HTTP/1.1). - + HTTPRequest(const std::string& method, const std::string& uri); /// Creates a HTTP/1.0 request with the given method and URI. HTTPRequest(const std::string& method, const std::string& uri, const std::string& version); /// Creates a HTTP request with the given method, URI and version. + HTTPRequest(const HTTPRequest& other); + /// Creates a HTTP request by copying another one. + virtual ~HTTPRequest(); /// Destroys the HTTPRequest. + HTTPRequest& operator = (const HTTPRequest&); + /// Assignment operator. + void setMethod(const std::string& method); /// Sets the method. @@ -60,20 +66,20 @@ public: void setURI(const std::string& uri); /// Sets the request URI. - + const std::string& getURI() const; /// Returns the request URI. - + void setHost(const std::string& host); /// Sets the value of the Host header field. - + void setHost(const std::string& host, Poco::UInt16 port); /// Sets the value of the Host header field. /// /// If the given port number is a non-standard /// port number (other than 80 or 443), it is /// included in the Host header field. - + const std::string& getHost() const; /// Returns the value of the Host header field. /// @@ -83,7 +89,7 @@ public: void setCookies(const NameValueCollection& cookies); /// Adds a Cookie header with the names and /// values from cookies. - + void getCookies(NameValueCollection& cookies) const; /// Fills cookies with the cookies extracted /// from the Cookie headers in the request. @@ -91,18 +97,21 @@ public: bool hasCredentials() const; /// Returns true iff the request contains authentication /// information in the form of an Authorization header. - + void getCredentials(std::string& scheme, std::string& authInfo) const; /// Returns the authentication scheme and additional authentication /// information contained in this request. /// /// Throws a NotAuthenticatedException if no authentication information /// is contained in the request. - + void setCredentials(const std::string& scheme, const std::string& authInfo); /// Sets the authentication scheme and information for /// this request. + void removeCredentials(); + /// Removes any credentials from the request. + bool getExpectContinue() const; /// Returns true if the request contains an /// "Expect: 100-continue" header. @@ -110,22 +119,25 @@ public: void setExpectContinue(bool expectContinue); /// Adds a "Expect: 100-continue" header to the request if /// expectContinue is true, otherwise removes the Expect header. - + bool hasProxyCredentials() const; /// Returns true iff the request contains proxy authentication /// information in the form of an Proxy-Authorization header. - + void getProxyCredentials(std::string& scheme, std::string& authInfo) const; /// Returns the proxy authentication scheme and additional proxy authentication /// information contained in this request. /// /// Throws a NotAuthenticatedException if no proxy authentication information /// is contained in the request. - + void setProxyCredentials(const std::string& scheme, const std::string& authInfo); /// Sets the proxy authentication scheme and information for /// this request. + void removeProxyCredentials(); + /// Removes any proxy credentials from the request. + void write(std::ostream& ostr) const; /// Writes the HTTP request to the given /// output stream. @@ -133,7 +145,7 @@ public: void read(std::istream& istr); /// Reads the HTTP request from the /// given input stream. - + static const std::string HTTP_GET; static const std::string HTTP_HEAD; static const std::string HTTP_PUT; @@ -143,7 +155,7 @@ public: static const std::string HTTP_TRACE; static const std::string HTTP_CONNECT; static const std::string HTTP_PATCH; - + static const std::string HOST; static const std::string COOKIE; static const std::string AUTHORIZATION; @@ -158,7 +170,7 @@ protected: /// /// Throws a NotAuthenticatedException if no authentication information /// is contained in the request. - + void setCredentials(const std::string& header, const std::string& scheme, const std::string& authInfo); /// Writes the authentication scheme and information for /// this request to the given header. @@ -170,12 +182,9 @@ private: MAX_URI_LENGTH = 16384, MAX_VERSION_LENGTH = 8 }; - + std::string _method; std::string _uri; - - HTTPRequest(const HTTPRequest&); - HTTPRequest& operator = (const HTTPRequest&); }; diff --git a/Net/include/Poco/Net/HTTPResponse.h b/Net/include/Poco/Net/HTTPResponse.h index fd6eb81c5..029095b2d 100644 --- a/Net/include/Poco/Net/HTTPResponse.h +++ b/Net/include/Poco/Net/HTTPResponse.h @@ -113,7 +113,7 @@ public: HTTPResponse(); /// Creates the HTTPResponse with OK status. - + HTTPResponse(HTTPStatus status, const std::string& reason); /// Creates the HTTPResponse with the given status /// and reason phrase. @@ -121,41 +121,47 @@ public: HTTPResponse(const std::string& version, HTTPStatus status, const std::string& reason); /// Creates the HTTPResponse with the given version, status /// and reason phrase. - - HTTPResponse(HTTPStatus status); + + explicit HTTPResponse(HTTPStatus status); /// Creates the HTTPResponse with the given status - /// an an appropriate reason phrase. + /// and an appropriate reason phrase. HTTPResponse(const std::string& version, HTTPStatus status); /// Creates the HTTPResponse with the given version, status - /// an an appropriate reason phrase. + /// and an appropriate reason phrase. + + HTTPResponse(const HTTPResponse& other); + /// Creates the HTTPResponse by copying another one. virtual ~HTTPResponse(); /// Destroys the HTTPResponse. + HTTPResponse& operator = (const HTTPResponse& other); + /// Assignment operator. + void setStatus(HTTPStatus status); /// Sets the HTTP status code. /// /// Does not change the reason phrase. - + HTTPStatus getStatus() const; /// Returns the HTTP status code. - + void setStatus(const std::string& status); /// Sets the HTTP status code. /// /// The string must contain a valid /// HTTP numerical status code. - + void setReason(const std::string& reason); /// Sets the HTTP reason phrase. - + const std::string& getReason() const; /// Returns the HTTP reason phrase. void setStatusAndReason(HTTPStatus status, const std::string& reason); /// Sets the HTTP status code and reason phrase. - + void setStatusAndReason(HTTPStatus status); /// Sets the HTTP status code and reason phrase. /// @@ -163,7 +169,7 @@ public: void setDate(const Poco::Timestamp& dateTime); /// Sets the Date header to the given date/time value. - + Poco::Timestamp getDate() const; /// Returns the value of the Date header. @@ -187,7 +193,7 @@ public: /// given input stream. /// /// 100 Continue responses are ignored. - + static const std::string& getReasonForStatus(HTTPStatus status); /// Returns an appropriate reason phrase /// for the given status code. @@ -254,7 +260,7 @@ public: static const std::string HTTP_REASON_NOT_EXTENDED; static const std::string HTTP_REASON_NETWORK_AUTHENTICATION_REQUIRED; static const std::string HTTP_REASON_UNKNOWN; - + static const std::string DATE; static const std::string SET_COOKIE; @@ -265,12 +271,9 @@ private: MAX_STATUS_LENGTH = 3, MAX_REASON_LENGTH = 512 }; - + HTTPStatus _status; std::string _reason; - - HTTPResponse(const HTTPResponse&); - HTTPResponse& operator = (const HTTPResponse&); }; diff --git a/Net/include/Poco/Net/NTLMCredentials.h b/Net/include/Poco/Net/NTLMCredentials.h new file mode 100644 index 000000000..852486b7b --- /dev/null +++ b/Net/include/Poco/Net/NTLMCredentials.h @@ -0,0 +1,171 @@ +// +// NTLMCredentials.h +// +// Library: Net +// Package: HTTP +// Module: NTLMCredentials +// +// Definition of the NTLMCredentials class. +// +// Copyright (c) 2019, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#ifndef Net_NTLMCredentials_INCLUDED +#define Net_NTLMCredentials_INCLUDED + + +#include "Poco/Net/Net.h" +#include "Poco/BinaryReader.h" +#include "Poco/BinaryWriter.h" +#include + + +namespace Poco { +namespace Net { + + +class Net_API NTLMCredentials + /// This is a utility class for working with + /// NTLMv2 Authentication. + /// + /// Note: This implementation is based on the + /// "The NTLM Authentication Protocol and Security Support Provider" + /// document written by Eric Glass and avilable from + /// http://davenport.sourceforge.net/ntlm.html + /// and the NT LAN Manager (NTLM) Authentication Protocol + /// [MS-NLMP] document by Microsoft. +{ +public: + enum + { + NTLM_MESSAGE_TYPE_NEGOTIATE = 0x01, + NTLM_MESSAGE_TYPE_CHALLENGE = 0x02, + NTLM_MESSAGE_TYPE_AUTHENTICATE = 0x03 + }; + + enum + { + NTLM_FLAG_NEGOTIATE_UNICODE = 0x00000001, + NTLM_FLAG_NEGOTIATE_OEM = 0x00000002, + NTLM_FLAG_REQUEST_TARGET = 0x00000004, + NTLM_FLAG_NEGOTIATE_NTLM = 0x00000200, + NTLM_FLAG_DOMAIN_SUPPLIED = 0x00001000, + NTLM_FLAG_WORKST_SUPPLIED = 0x00002000, + NTLM_FLAG_NEGOTIATE_LOCAL = 0x00004000, + NTLM_FLAG_NEGOTIATE_ALWAYS_SIGN = 0x00008000, + NTLM_FLAG_NEGOTIATE_NTLM2_KEY = 0x00080000, + NTLM_FLAG_TARGET_DOMAIN = 0x00010000, + NTLM_FLAG_TARGET_SERVER = 0x00020000, + NTLM_FLAG_TARGET_SHARE = 0x00040000, + NTLM_FLAG_NEGOTIATE_TARGET = 0x00800000, + NTLM_FLAG_NEGOTIATE_128 = 0x20000000, + NTLM_FLAG_NEGOTIATE_56 = 0x80000000 + }; + + struct NegotiateMessage + /// This message is sent from the client to initiate NTLM authentication. + { + NegotiateMessage(): flags(0) {} + + Poco::UInt32 flags; + std::string domain; + std::string workstation; + }; + + struct ChallengeMessage + /// This message is sent back by the server and contains the NTLM challenge. + { + ChallengeMessage(): flags(0) {} + + Poco::UInt32 flags; + std::vector challenge; + std::string target; + std::vector targetInfo; + }; + + struct AuthenticateMessage + /// This message is sent from the client to authenticate itself by providing + /// a response to the server challenge. + { + AuthenticateMessage(): flags(0) {} + + Poco::UInt32 flags; + std::vector lmResponse; + std::vector ntlmResponse; + std::string target; + std::string username; + std::string workstation; + }; + + struct BufferDesc + { + BufferDesc(): + length(0), + reserved(0), + offset(0) + { + } + + BufferDesc(Poco::UInt16 len, Poco::UInt32 off): + length(len), + reserved(len), + offset(off) + { + } + + Poco::UInt16 length; + Poco::UInt16 reserved; + Poco::UInt32 offset; + }; + + static std::vector createNonce(); + /// Creates an 8-byte client nonce for NTLM authentication. + + static Poco::UInt64 createTimestamp(); + /// Creates the NTLM timestamp in tenths of a microsecond since January 1, 1601, + /// using the current system time. + + static std::vector createPasswordHash(const std::string& password); + /// Creates the NTLM password hash (MD4 of UTF-16-converted password). + + static std::vector createNTLMv2Hash(const std::string& username, const std::string& target, const std::string& password); + /// Creates the NTLMv2 hash, which is the HMAC-MD5 of the concatenated UTF-16 uppercase username and target, + /// using the password hash as HMAC passphrase. + + static std::vector createLMv2Response(const std::vector& ntlm2Hash, const std::vector& challenge, const std::vector& nonce); + /// Creates the LMv2 response by computing the HMAC-MD5 of the challenge and nonce, using the + /// ntlm2Hash (see createNTLMv2Hash()) as HMAC passphrase. + + static std::vector createNTLMv2Response(const std::vector& ntlm2Hash, const std::vector& challenge, const std::vector& nonce, const std::vector& targetInfo, Poco::UInt64 timestamp); + /// Creates the NTLMv2 response by creating the "blob" and prepending its HMAC-MD5, using the ntlm2Hash as HMAC passphrase. + + static std::vector formatNegotiateMessage(const NegotiateMessage& message); + /// Creates the NTLM Type 1 Negotiate message used for initiating NTLM authentication from the client. + + static bool parseChallengeMessage(const unsigned char* buffer, std::size_t size, ChallengeMessage& message); + /// Parses a NTLM Type 2 Challenge message. + /// + /// Returns true if the message was parsed successfully, otherwise false. + + static std::vector formatAuthenticateMessage(const AuthenticateMessage& message); + /// Creates the NTLM Type 1 Authenticate message used for initiating NTLM authentication from the client. + + static void readBufferDesc(Poco::BinaryReader& reader, BufferDesc& desc); + /// Reads a buffer descriptor. + + static void writeBufferDesc(Poco::BinaryWriter& writer, const BufferDesc& desc); + /// Writes a buffer descriptor. + + static const std::string NTLMSSP; + /// Message signature string. +}; + + +} } // namespace Poco::Net + + +#endif // Net_NTLMCredentials_INCLUDED diff --git a/Net/src/HTTPAuthenticationParams.cpp b/Net/src/HTTPAuthenticationParams.cpp index 26adedf19..7eddd0b54 100644 --- a/Net/src/HTTPAuthenticationParams.cpp +++ b/Net/src/HTTPAuthenticationParams.cpp @@ -13,13 +13,13 @@ // -#include "Poco/Exception.h" #include "Poco/Net/HTTPAuthenticationParams.h" #include "Poco/Net/HTTPRequest.h" #include "Poco/Net/HTTPResponse.h" #include "Poco/Net/NetException.h" #include "Poco/String.h" #include "Poco/Ascii.h" +#include "Poco/Exception.h" using Poco::icompare; @@ -66,6 +66,7 @@ namespace Net { const std::string HTTPAuthenticationParams::REALM("realm"); +const std::string HTTPAuthenticationParams::NTLM("NTLM"); const std::string HTTPAuthenticationParams::WWW_AUTHENTICATE("WWW-Authenticate"); const std::string HTTPAuthenticationParams::PROXY_AUTHENTICATE("Proxy-Authenticate"); @@ -135,20 +136,25 @@ void HTTPAuthenticationParams::fromResponse(const HTTPResponse& response, const bool found = false; while (!found && it != response.end() && icompare(it->first, header) == 0) { - const std::string& header = it->second; - if (icompare(header, 0, 6, "Basic ") == 0) + const std::string& headerValue = it->second; + if (icompare(headerValue, 0, 6, "Basic ") == 0) { - parse(header.begin() + 6, header.end()); + parse(headerValue.begin() + 6, headerValue.end()); found = true; } - else if (icompare(header, 0, 7, "Digest ") == 0) + else if (icompare(headerValue, 0, 7, "Digest ") == 0) { - parse(header.begin() + 7, header.end()); + parse(headerValue.begin() + 7, headerValue.end()); + found = true; + } + else if (icompare(headerValue, 0, 5, "NTLM ") == 0) + { + set(NTLM, headerValue.substr(5)); found = true; } ++it; } - if (!found) throw NotAuthenticatedException("No Basic or Digest authentication header found"); + if (!found) throw NotAuthenticatedException("No Basic, Digest or NTLM authentication header found"); } @@ -166,21 +172,27 @@ void HTTPAuthenticationParams::setRealm(const std::string& realm) std::string HTTPAuthenticationParams::toString() const { - ConstIterator iter = begin(); std::string result; - - if (iter != end()) + if (size() == 1 && find(NTLM) != end()) { - formatParameter(result, iter->first, iter->second); - ++iter; + result = get(NTLM); } - - for (; iter != end(); ++iter) + else { - result.append(", "); - formatParameter(result, iter->first, iter->second); - } + ConstIterator iter = begin(); + if (iter != end()) + { + formatParameter(result, iter->first, iter->second); + ++iter; + } + + for (; iter != end(); ++iter) + { + result.append(", "); + formatParameter(result, iter->first, iter->second); + } + } return result; } diff --git a/Net/src/HTTPClientSession.cpp b/Net/src/HTTPClientSession.cpp index 59c7535eb..f1ce3b34d 100644 --- a/Net/src/HTTPClientSession.cpp +++ b/Net/src/HTTPClientSession.cpp @@ -19,7 +19,7 @@ #include "Poco/Net/HTTPStream.h" #include "Poco/Net/HTTPFixedLengthStream.h" #include "Poco/Net/HTTPChunkedStream.h" -#include "Poco/Net/HTTPBasicCredentials.h" +#include "Poco/Net/HTTPCredentials.h" #include "Poco/Net/NetException.h" #include "Poco/NumberFormatter.h" #include "Poco/CountingStream.h" @@ -45,7 +45,8 @@ HTTPClientSession::HTTPClientSession(): _reconnect(false), _mustReconnect(false), _expectResponseBody(false), - _responseReceived(false) + _responseReceived(false), + _ntlmProxyAuthenticated(false) { } @@ -58,7 +59,8 @@ HTTPClientSession::HTTPClientSession(const StreamSocket& socket): _reconnect(false), _mustReconnect(false), _expectResponseBody(false), - _responseReceived(false) + _responseReceived(false), + _ntlmProxyAuthenticated(false) { } @@ -71,7 +73,8 @@ HTTPClientSession::HTTPClientSession(const SocketAddress& address): _reconnect(false), _mustReconnect(false), _expectResponseBody(false), - _responseReceived(false) + _responseReceived(false), + _ntlmProxyAuthenticated(false) { } @@ -84,7 +87,8 @@ HTTPClientSession::HTTPClientSession(const std::string& host, Poco::UInt16 port) _reconnect(false), _mustReconnect(false), _expectResponseBody(false), - _responseReceived(false) + _responseReceived(false), + _ntlmProxyAuthenticated(false) { } @@ -97,7 +101,8 @@ HTTPClientSession::HTTPClientSession(const std::string& host, Poco::UInt16 port, _reconnect(false), _mustReconnect(false), _expectResponseBody(false), - _responseReceived(false) + _responseReceived(false), + _ntlmProxyAuthenticated(false) { } @@ -195,8 +200,6 @@ std::ostream& HTTPClientSession::sendRequest(HTTPRequest& request) { _pRequestStream = 0; _pResponseStream = 0; - clearException(); - _responseReceived = false; bool keepAlive = getKeepAlive(); if (((connected() && !keepAlive) || mustReconnect()) && !_host.empty()) @@ -207,50 +210,28 @@ std::ostream& HTTPClientSession::sendRequest(HTTPRequest& request) try { if (!connected()) + { + _ntlmProxyAuthenticated = false; reconnect(); + } if (!keepAlive) + { request.setKeepAlive(false); + } if (!request.has(HTTPRequest::HOST) && !_host.empty()) + { request.setHost(_host, _port); + } if (!_proxyConfig.host.empty() && !bypassProxy()) { - request.setURI(proxyRequestPrefix() + request.getURI()); + std::string prefix = proxyRequestPrefix(); + if (!prefix.empty() && request.getURI().compare(0, 7, "http://") != 0 && request.getURI().compare(0, 8, "https://") != 0) + request.setURI(prefix + request.getURI()); + if (keepAlive) request.set(HTTPMessage::PROXY_CONNECTION, HTTPMessage::CONNECTION_KEEP_ALIVE); proxyAuthenticate(request); } _reconnect = keepAlive; - _expectResponseBody = request.getMethod() != HTTPRequest::HTTP_HEAD; - const std::string& method = request.getMethod(); - if (request.getChunkedTransferEncoding()) - { - HTTPHeaderOutputStream hos(*this); - request.write(hos); - _pRequestStream = new HTTPChunkedOutputStream(*this); - } - else if (request.hasContentLength()) - { - Poco::CountingOutputStream cs; - request.write(cs); -#if POCO_HAVE_INT64 - _pRequestStream = new HTTPFixedLengthOutputStream(*this, request.getContentLength64() + cs.chars()); -#else - _pRequestStream = new HTTPFixedLengthOutputStream(*this, request.getContentLength() + cs.chars()); -#endif - request.write(*_pRequestStream); - } - else if ((method != HTTPRequest::HTTP_PUT && method != HTTPRequest::HTTP_POST && method != HTTPRequest::HTTP_PATCH) || request.has(HTTPRequest::UPGRADE)) - { - Poco::CountingOutputStream cs; - request.write(cs); - _pRequestStream = new HTTPFixedLengthOutputStream(*this, cs.chars()); - request.write(*_pRequestStream); - } - else - { - _pRequestStream = new HTTPOutputStream(*this); - request.write(*_pRequestStream); - } - _lastRequest.update(); - return *_pRequestStream; + return sendRequestImpl(request); } catch (Exception&) { @@ -260,6 +241,48 @@ std::ostream& HTTPClientSession::sendRequest(HTTPRequest& request) } +std::ostream& HTTPClientSession::sendRequestImpl(const HTTPRequest& request) +{ + _pRequestStream = 0; + _pResponseStream = 0; + clearException(); + _responseReceived = false; + _expectResponseBody = request.getMethod() != HTTPRequest::HTTP_HEAD; + const std::string& method = request.getMethod(); + if (request.getChunkedTransferEncoding()) + { + HTTPHeaderOutputStream hos(*this); + request.write(hos); + _pRequestStream = new HTTPChunkedOutputStream(*this); + } + else if (request.hasContentLength()) + { + Poco::CountingOutputStream cs; + request.write(cs); +#if POCO_HAVE_INT64 + _pRequestStream = new HTTPFixedLengthOutputStream(*this, request.getContentLength64() + cs.chars()); +#else + _pRequestStream = new HTTPFixedLengthOutputStream(*this, request.getContentLength() + cs.chars()); +#endif + request.write(*_pRequestStream); + } + else if ((method != HTTPRequest::HTTP_PUT && method != HTTPRequest::HTTP_POST && method != HTTPRequest::HTTP_PATCH) || request.has(HTTPRequest::UPGRADE)) + { + Poco::CountingOutputStream cs; + request.write(cs); + _pRequestStream = new HTTPFixedLengthOutputStream(*this, cs.chars()); + request.write(*_pRequestStream); + } + else + { + _pRequestStream = new HTTPOutputStream(*this); + request.write(*_pRequestStream); + } + _lastRequest.update(); + return *_pRequestStream; +} + + std::istream& HTTPClientSession::receiveResponse(HTTPResponse& response) { _pRequestStream = 0; @@ -409,20 +432,89 @@ bool HTTPClientSession::mustReconnect() const void HTTPClientSession::proxyAuthenticate(HTTPRequest& request) { - proxyAuthenticateImpl(request); + proxyAuthenticateImpl(request, _proxyConfig); } -void HTTPClientSession::proxyAuthenticateImpl(HTTPRequest& request) +void HTTPClientSession::proxyAuthenticateImpl(HTTPRequest& request, const ProxyConfig& proxyConfig) { - if (!_proxyConfig.username.empty()) + switch (proxyConfig.authMethod) { - HTTPBasicCredentials creds(_proxyConfig.username, _proxyConfig.password); - creds.proxyAuthenticate(request); + case PROXY_AUTH_NONE: + break; + + case PROXY_AUTH_HTTP_BASIC: + _proxyBasicCreds.setUsername(proxyConfig.username); + _proxyBasicCreds.setPassword(proxyConfig.password); + _proxyBasicCreds.proxyAuthenticate(request); + break; + + case PROXY_AUTH_HTTP_DIGEST: + if (HTTPCredentials::hasDigestCredentials(request)) + { + _proxyDigestCreds.updateProxyAuthInfo(request); + } + else + { + _proxyDigestCreds.setUsername(proxyConfig.username); + _proxyDigestCreds.setPassword(proxyConfig.password); + proxyAuthenticateDigest(request); + } + + case PROXY_AUTH_NTLM: + if (_ntlmProxyAuthenticated) + { + _proxyNTLMCreds.updateProxyAuthInfo(request); + } + else + { + _proxyNTLMCreds.setUsername(proxyConfig.username); + _proxyNTLMCreds.setPassword(proxyConfig.password); + proxyAuthenticateNTLM(request); + _ntlmProxyAuthenticated = true; + } } } +void HTTPClientSession::proxyAuthenticateDigest(HTTPRequest& request) +{ + HTTPResponse response; + request.set(HTTPMessage::PROXY_CONNECTION, HTTPMessage::CONNECTION_KEEP_ALIVE); + sendChallengeRequest(request, response); + _proxyDigestCreds.proxyAuthenticate(request, response); +} + + +void HTTPClientSession::proxyAuthenticateNTLM(HTTPRequest& request) +{ + HTTPResponse response; + request.set(HTTPMessage::PROXY_CONNECTION, HTTPMessage::CONNECTION_KEEP_ALIVE); + _proxyNTLMCreds.proxyAuthenticate(request, std::string()); + sendChallengeRequest(request, response); + _proxyNTLMCreds.proxyAuthenticate(request, response); +} + + +void HTTPClientSession::sendChallengeRequest(const HTTPRequest& request, HTTPResponse& response) +{ + if (!connected()) + { + reconnect(); + } + + HTTPRequest challengeRequest(request); + if (challengeRequest.hasContentLength()) + { + challengeRequest.setContentLength(0); + } + + sendRequestImpl(challengeRequest); + std::istream& istr = receiveResponse(response); + while (istr.good()) istr.get(); +} + + StreamSocket HTTPClientSession::proxyConnect() { ProxyConfig emptyProxyConfig; @@ -433,9 +525,9 @@ StreamSocket HTTPClientSession::proxyConnect() NumberFormatter::append(targetAddress, _port); HTTPRequest proxyRequest(HTTPRequest::HTTP_CONNECT, targetAddress, HTTPMessage::HTTP_1_1); HTTPResponse proxyResponse; - proxyRequest.set("Proxy-Connection", "keep-alive"); - proxyRequest.set("Host", getHost()); - proxyAuthenticateImpl(proxyRequest); + proxyRequest.set(HTTPMessage::PROXY_CONNECTION, HTTPMessage::CONNECTION_KEEP_ALIVE); + proxyRequest.set(HTTPRequest::HOST, getHost()); + proxySession.proxyAuthenticateImpl(proxyRequest, _proxyConfig); proxySession.setKeepAlive(true); proxySession.sendRequest(proxyRequest); proxySession.receiveResponse(proxyResponse); diff --git a/Net/src/HTTPCredentials.cpp b/Net/src/HTTPCredentials.cpp index ae1d6ea0b..43b51783b 100644 --- a/Net/src/HTTPCredentials.cpp +++ b/Net/src/HTTPCredentials.cpp @@ -75,34 +75,45 @@ void HTTPCredentials::authenticate(HTTPRequest& request, const HTTPResponse& res { for (HTTPResponse::ConstIterator iter = response.find(HTTPAuthenticationParams::WWW_AUTHENTICATE); iter != response.end(); ++iter) { - if (isBasicCredentials(iter->second)) + if (isBasicCredentials(iter->second)) { HTTPBasicCredentials(_digest.getUsername(), _digest.getPassword()).authenticate(request); return; - } - else if (isDigestCredentials(iter->second)) + } + else if (isDigestCredentials(iter->second)) { _digest.authenticate(request, HTTPAuthenticationParams(iter->second.substr(7))); return; } + else if (isNTLMCredentials(iter->second)) + { + _ntlm.setUsername(_digest.getUsername()); + _ntlm.setPassword(_digest.getPassword()); + _ntlm.authenticate(request, iter->second.substr(5)); + return; + } } } void HTTPCredentials::updateAuthInfo(HTTPRequest& request) { - if (request.has(HTTPRequest::AUTHORIZATION)) + if (request.has(HTTPRequest::AUTHORIZATION)) { const std::string& authorization = request.get(HTTPRequest::AUTHORIZATION); - if (isBasicCredentials(authorization)) + if (isBasicCredentials(authorization)) { HTTPBasicCredentials(_digest.getUsername(), _digest.getPassword()).authenticate(request); - } - else if (isDigestCredentials(authorization)) + } + else if (isDigestCredentials(authorization)) { _digest.updateAuthInfo(request); } + else if (isNTLMCredentials(authorization)) + { + _ntlm.updateAuthInfo(request); + } } } @@ -111,34 +122,45 @@ void HTTPCredentials::proxyAuthenticate(HTTPRequest& request, const HTTPResponse { for (HTTPResponse::ConstIterator iter = response.find(HTTPAuthenticationParams::PROXY_AUTHENTICATE); iter != response.end(); ++iter) { - if (isBasicCredentials(iter->second)) + if (isBasicCredentials(iter->second)) { HTTPBasicCredentials(_digest.getUsername(), _digest.getPassword()).proxyAuthenticate(request); return; - } - else if (isDigestCredentials(iter->second)) + } + else if (isDigestCredentials(iter->second)) { _digest.proxyAuthenticate(request, HTTPAuthenticationParams(iter->second.substr(7))); return; } + else if (isNTLMCredentials(iter->second)) + { + _ntlm.setUsername(_digest.getUsername()); + _ntlm.setPassword(_digest.getPassword()); + _ntlm.proxyAuthenticate(request, iter->second.substr(5)); + return; + } } } void HTTPCredentials::updateProxyAuthInfo(HTTPRequest& request) { - if (request.has(HTTPRequest::PROXY_AUTHORIZATION)) + if (request.has(HTTPRequest::PROXY_AUTHORIZATION)) { const std::string& authorization = request.get(HTTPRequest::PROXY_AUTHORIZATION); - if (isBasicCredentials(authorization)) + if (isBasicCredentials(authorization)) { HTTPBasicCredentials(_digest.getUsername(), _digest.getPassword()).proxyAuthenticate(request); - } - else if (isDigestCredentials(authorization)) + } + else if (isDigestCredentials(authorization)) { _digest.updateProxyAuthInfo(request); } + else if (isNTLMCredentials(authorization)) + { + _ntlm.updateProxyAuthInfo(request); + } } } @@ -155,6 +177,12 @@ bool HTTPCredentials::isDigestCredentials(const std::string& header) } +bool HTTPCredentials::isNTLMCredentials(const std::string& header) +{ + return icompare(header, 0, 4, "NTLM") == 0 && (header.size() > 5 ? Poco::Ascii::isSpace(header[5]) : true); +} + + bool HTTPCredentials::hasBasicCredentials(const HTTPRequest& request) { return request.has(HTTPRequest::AUTHORIZATION) && isBasicCredentials(request.get(HTTPRequest::AUTHORIZATION)); @@ -167,6 +195,12 @@ bool HTTPCredentials::hasDigestCredentials(const HTTPRequest& request) } +bool HTTPCredentials::hasNTLMCredentials(const HTTPRequest& request) +{ + return request.has(HTTPRequest::AUTHORIZATION) && isNTLMCredentials(request.get(HTTPRequest::AUTHORIZATION)); +} + + bool HTTPCredentials::hasProxyBasicCredentials(const HTTPRequest& request) { return request.has(HTTPRequest::PROXY_AUTHORIZATION) && isBasicCredentials(request.get(HTTPRequest::PROXY_AUTHORIZATION)); @@ -179,16 +213,22 @@ bool HTTPCredentials::hasProxyDigestCredentials(const HTTPRequest& request) } +bool HTTPCredentials::hasProxyNTLMCredentials(const HTTPRequest& request) +{ + return request.has(HTTPRequest::PROXY_AUTHORIZATION) && isNTLMCredentials(request.get(HTTPRequest::PROXY_AUTHORIZATION)); +} + + void HTTPCredentials::extractCredentials(const std::string& userInfo, std::string& username, std::string& password) { const std::string::size_type p = userInfo.find(':'); - if (p != std::string::npos) + if (p != std::string::npos) { username.assign(userInfo, 0, p); password.assign(userInfo, p + 1, std::string::npos); - } - else + } + else { username.assign(userInfo); password.clear(); @@ -198,7 +238,7 @@ void HTTPCredentials::extractCredentials(const std::string& userInfo, std::strin void HTTPCredentials::extractCredentials(const Poco::URI& uri, std::string& username, std::string& password) { - if (!uri.getUserInfo().empty()) + if (!uri.getUserInfo().empty()) { extractCredentials(uri.getUserInfo(), username, password); } diff --git a/Net/src/HTTPMessage.cpp b/Net/src/HTTPMessage.cpp index debda04c3..5da55e0f5 100644 --- a/Net/src/HTTPMessage.cpp +++ b/Net/src/HTTPMessage.cpp @@ -38,6 +38,7 @@ const std::string HTTPMessage::CONTENT_LENGTH = "Content-Length"; const std::string HTTPMessage::CONTENT_TYPE = "Content-Type"; const std::string HTTPMessage::TRANSFER_ENCODING = "Transfer-Encoding"; const std::string HTTPMessage::CONNECTION = "Connection"; +const std::string HTTPMessage::PROXY_CONNECTION = "Proxy-Connection"; const std::string HTTPMessage::CONNECTION_KEEP_ALIVE = "Keep-Alive"; const std::string HTTPMessage::CONNECTION_CLOSE = "Close"; const std::string HTTPMessage::EMPTY; @@ -55,11 +56,29 @@ HTTPMessage::HTTPMessage(const std::string& version): } +HTTPMessage::HTTPMessage(const HTTPMessage& other): + MessageHeader(other), + _version(other._version) +{ +} + + HTTPMessage::~HTTPMessage() { } +HTTPMessage& HTTPMessage::operator = (const HTTPMessage& other) +{ + if (this != &other) + { + MessageHeader::operator = (other); + _version = other._version; + } + return *this; +} + + void HTTPMessage::setVersion(const std::string& version) { _version = version; @@ -74,7 +93,7 @@ void HTTPMessage::setContentLength(std::streamsize length) erase(CONTENT_LENGTH); } - + std::streamsize HTTPMessage::getContentLength() const { const std::string& contentLength = get(CONTENT_LENGTH, EMPTY); @@ -89,7 +108,7 @@ std::streamsize HTTPMessage::getContentLength() const } -#if defined(POCO_HAVE_INT64) +#if defined(POCO_HAVE_INT64) void HTTPMessage::setContentLength64(Poco::Int64 length) { if (length != UNKNOWN_CONTENT_LENGTH) @@ -98,7 +117,7 @@ void HTTPMessage::setContentLength64(Poco::Int64 length) erase(CONTENT_LENGTH); } - + Poco::Int64 HTTPMessage::getContentLength64() const { const std::string& contentLength = get(CONTENT_LENGTH, EMPTY); @@ -108,7 +127,7 @@ Poco::Int64 HTTPMessage::getContentLength64() const } else return UNKNOWN_CONTENT_LENGTH; } -#endif // defined(POCO_HAVE_INT64) +#endif // defined(POCO_HAVE_INT64) void HTTPMessage::setTransferEncoding(const std::string& transferEncoding) @@ -134,13 +153,13 @@ void HTTPMessage::setChunkedTransferEncoding(bool flag) setTransferEncoding(IDENTITY_TRANSFER_ENCODING); } - + bool HTTPMessage::getChunkedTransferEncoding() const { return icompare(getTransferEncoding(), CHUNKED_TRANSFER_ENCODING) == 0; } - + void HTTPMessage::setContentType(const std::string& mediaType) { if (mediaType.empty()) @@ -155,7 +174,7 @@ void HTTPMessage::setContentType(const MediaType& mediaType) setContentType(mediaType.toString()); } - + const std::string& HTTPMessage::getContentType() const { return get(CONTENT_TYPE, UNKNOWN_CONTENT_TYPE); diff --git a/Net/src/HTTPNTLMCredentials.cpp b/Net/src/HTTPNTLMCredentials.cpp new file mode 100644 index 000000000..3235f4dcb --- /dev/null +++ b/Net/src/HTTPNTLMCredentials.cpp @@ -0,0 +1,190 @@ +// +// HTTPNTLMCredentials.cpp +// +// Library: Net +// Package: HTTP +// Module: HTTPNTLMCredentials +// +// Copyright (c) 2019, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#include "Poco/Net/HTTPNTLMCredentials.h" +#include "Poco/Net/NTLMCredentials.h" +#include "Poco/Net/HTTPAuthenticationParams.h" +#include "Poco/Net/HTTPRequest.h" +#include "Poco/Net/HTTPResponse.h" +#include "Poco/DateTime.h" +#include "Poco/NumberFormatter.h" +#include "Poco/Exception.h" +#include "Poco/Base64Encoder.h" +#include "Poco/Base64Decoder.h" +#include "Poco/MemoryStream.h" +#include + + +namespace Poco { +namespace Net { + + +const std::string HTTPNTLMCredentials::SCHEME = "NTLM"; + + +HTTPNTLMCredentials::HTTPNTLMCredentials() +{ +} + + +HTTPNTLMCredentials::HTTPNTLMCredentials(const std::string& username, const std::string& password): + _username(username), + _password(password) +{ +} + + +HTTPNTLMCredentials::~HTTPNTLMCredentials() +{ +} + + +void HTTPNTLMCredentials::reset() +{ +} + + +void HTTPNTLMCredentials::setUsername(const std::string& username) +{ + _username = username; +} + + +void HTTPNTLMCredentials::setPassword(const std::string& password) +{ + _password = password; +} + + +void HTTPNTLMCredentials::authenticate(HTTPRequest& request, const HTTPResponse& response) +{ + HTTPAuthenticationParams params(response); + authenticate(request, params.get(HTTPAuthenticationParams::NTLM, "")); +} + + +void HTTPNTLMCredentials::authenticate(HTTPRequest& request, const std::string& ntlmChallengeBase64) +{ + std::string ntlmMessage = createNTLMMessage(ntlmChallengeBase64); + request.setCredentials(SCHEME, ntlmMessage); +} + + +void HTTPNTLMCredentials::updateAuthInfo(HTTPRequest& request) +{ + request.removeCredentials(); +} + + +void HTTPNTLMCredentials::proxyAuthenticate(HTTPRequest& request, const HTTPResponse& response) +{ + HTTPAuthenticationParams params(response, HTTPAuthenticationParams::PROXY_AUTHENTICATE); + proxyAuthenticate(request, params.get(HTTPAuthenticationParams::NTLM, "")); +} + + +void HTTPNTLMCredentials::proxyAuthenticate(HTTPRequest& request, const std::string& ntlmChallengeBase64) +{ + std::string ntlmMessage = createNTLMMessage(ntlmChallengeBase64); + request.setProxyCredentials(SCHEME, ntlmMessage); +} + + +void HTTPNTLMCredentials::updateProxyAuthInfo(HTTPRequest& request) +{ + request.removeProxyCredentials(); +} + + +std::string HTTPNTLMCredentials::createNTLMMessage(const std::string& responseAuthParams) +{ + if (responseAuthParams.empty()) + { + NTLMCredentials::NegotiateMessage negotiateMsg; + std::string username; + splitUsername(_username, username, negotiateMsg.domain); + std::vector negotiateBuf = NTLMCredentials::formatNegotiateMessage(negotiateMsg); + + std::ostringstream ostr; + Poco::Base64Encoder base64(ostr); + base64.rdbuf()->setLineLength(0); + base64.write(reinterpret_cast(&negotiateBuf[0]), negotiateBuf.size()); + base64.close(); + return ostr.str(); + } + else + { + Poco::MemoryInputStream istr(responseAuthParams.data(), responseAuthParams.size()); + Poco::Base64Decoder debase64(istr); + std::vector buffer(responseAuthParams.size()); + debase64.read(reinterpret_cast(&buffer[0]), buffer.size()); + std::size_t size = debase64.gcount(); + + Poco::Net::NTLMCredentials::ChallengeMessage challengeMsg; + if (NTLMCredentials::parseChallengeMessage(&buffer[0], size, challengeMsg)) + { + std::string username; + std::string domain; + splitUsername(_username, username, domain); + + NTLMCredentials::AuthenticateMessage authenticateMsg; + authenticateMsg.flags = challengeMsg.flags; + authenticateMsg.target = challengeMsg.target; + authenticateMsg.username = username; + + std::vector nonce = NTLMCredentials::createNonce(); + Poco::UInt64 timestamp = NTLMCredentials::createTimestamp(); + std::vector ntlm2Hash = NTLMCredentials::createNTLMv2Hash(username, challengeMsg.target, _password); + authenticateMsg.lmResponse = NTLMCredentials::createLMv2Response(ntlm2Hash, challengeMsg.challenge, nonce); + authenticateMsg.ntlmResponse = Poco::Net::NTLMCredentials::createNTLMv2Response(ntlm2Hash, challengeMsg.challenge, nonce, challengeMsg.targetInfo, timestamp); + + std::vector authenticateBuf = Poco::Net::NTLMCredentials::formatAuthenticateMessage(authenticateMsg); + + std::ostringstream ostr; + Poco::Base64Encoder base64(ostr); + base64.rdbuf()->setLineLength(0); + base64.write(reinterpret_cast(&authenticateBuf[0]), authenticateBuf.size()); + base64.close(); + return ostr.str(); + } + else throw Poco::InvalidArgumentException("Invalid NTLM challenge"); + } +} + + +void HTTPNTLMCredentials::splitUsername(const std::string& usernameAndDomain, std::string& username, std::string& domain) +{ + std::string::size_type pos = usernameAndDomain.find('\\'); + if (pos != std::string::npos) + { + domain.assign(usernameAndDomain, 0, pos); + username.assign(usernameAndDomain, pos + 1); + return; + } + else + { + pos = usernameAndDomain.find('@'); + if (pos != std::string::npos) + { + username.assign(usernameAndDomain, 0, pos); + domain.assign(usernameAndDomain, pos + 1); + return; + } + } + username = usernameAndDomain; +} + + + +} } // namespace Poco::Net diff --git a/Net/src/HTTPRequest.cpp b/Net/src/HTTPRequest.cpp index 52b3cf2f7..2759c1f55 100644 --- a/Net/src/HTTPRequest.cpp +++ b/Net/src/HTTPRequest.cpp @@ -50,7 +50,7 @@ HTTPRequest::HTTPRequest(): { } - + HTTPRequest::HTTPRequest(const std::string& version): HTTPMessage(version), _method(HTTP_GET), @@ -58,7 +58,7 @@ HTTPRequest::HTTPRequest(const std::string& version): { } - + HTTPRequest::HTTPRequest(const std::string& method, const std::string& uri): _method(method), _uri(uri) @@ -74,11 +74,31 @@ HTTPRequest::HTTPRequest(const std::string& method, const std::string& uri, cons } +HTTPRequest::HTTPRequest(const HTTPRequest& other): + HTTPMessage(other), + _method(other._method), + _uri(other._uri) +{ +} + + HTTPRequest::~HTTPRequest() { } +HTTPRequest& HTTPRequest::operator = (const HTTPRequest& other) +{ + if (this != &other) + { + HTTPMessage::operator = (other); + _method = other._method; + _uri = other._uri; + } + return *this; +} + + void HTTPRequest::setMethod(const std::string& method) { _method = method; @@ -96,7 +116,7 @@ void HTTPRequest::setHost(const std::string& host) set(HOST, host); } - + void HTTPRequest::setHost(const std::string& host, Poco::UInt16 port) { std::string value; @@ -110,7 +130,7 @@ void HTTPRequest::setHost(const std::string& host, Poco::UInt16 port) else { value.append(host); - } + } if (port != 80 && port != 443) { @@ -120,7 +140,7 @@ void HTTPRequest::setHost(const std::string& host, Poco::UInt16 port) setHost(value); } - + const std::string& HTTPRequest::getHost() const { return get(HOST); @@ -142,7 +162,7 @@ void HTTPRequest::setCookies(const NameValueCollection& cookies) add(COOKIE, cookie); } - + void HTTPRequest::getCookies(NameValueCollection& cookies) const { NameValueCollection::ConstIterator it = find(COOKIE); @@ -159,37 +179,49 @@ bool HTTPRequest::hasCredentials() const return has(AUTHORIZATION); } - + void HTTPRequest::getCredentials(std::string& scheme, std::string& authInfo) const { getCredentials(AUTHORIZATION, scheme, authInfo); } - + void HTTPRequest::setCredentials(const std::string& scheme, const std::string& authInfo) { setCredentials(AUTHORIZATION, scheme, authInfo); } +void HTTPRequest::removeCredentials() +{ + erase(AUTHORIZATION); +} + + bool HTTPRequest::hasProxyCredentials() const { return has(PROXY_AUTHORIZATION); } - + void HTTPRequest::getProxyCredentials(std::string& scheme, std::string& authInfo) const { getCredentials(PROXY_AUTHORIZATION, scheme, authInfo); } - + void HTTPRequest::setProxyCredentials(const std::string& scheme, const std::string& authInfo) { setCredentials(PROXY_AUTHORIZATION, scheme, authInfo); } +void HTTPRequest::removeProxyCredentials() +{ + erase(PROXY_AUTHORIZATION); +} + + void HTTPRequest::write(std::ostream& ostr) const { ostr << _method << " " << _uri << " " << getVersion() << "\r\n"; @@ -248,7 +280,7 @@ void HTTPRequest::getCredentials(const std::string& header, std::string& scheme, else throw NotAuthenticatedException(); } - + void HTTPRequest::setCredentials(const std::string& header, const std::string& scheme, const std::string& authInfo) { std::string auth(scheme); diff --git a/Net/src/HTTPResponse.cpp b/Net/src/HTTPResponse.cpp index b9deb991e..e19076d6d 100644 --- a/Net/src/HTTPResponse.cpp +++ b/Net/src/HTTPResponse.cpp @@ -108,7 +108,7 @@ HTTPResponse::HTTPResponse(): { } - + HTTPResponse::HTTPResponse(HTTPStatus status, const std::string& reason): _status(status), _reason(reason) @@ -116,7 +116,7 @@ HTTPResponse::HTTPResponse(HTTPStatus status, const std::string& reason): } - + HTTPResponse::HTTPResponse(const std::string& version, HTTPStatus status, const std::string& reason): HTTPMessage(version), _status(status), @@ -124,7 +124,7 @@ HTTPResponse::HTTPResponse(const std::string& version, HTTPStatus status, const { } - + HTTPResponse::HTTPResponse(HTTPStatus status): _status(status), _reason(getReasonForStatus(status)) @@ -140,11 +140,31 @@ HTTPResponse::HTTPResponse(const std::string& version, HTTPStatus status): } +HTTPResponse::HTTPResponse(const HTTPResponse& other): + HTTPMessage(other), + _status(other._status), + _reason(other._reason) +{ +} + + HTTPResponse::~HTTPResponse() { } +HTTPResponse& HTTPResponse::operator = (const HTTPResponse& other) +{ + if (this != &other) + { + HTTPMessage::operator = (other); + _status = other._status; + _reason = other._reason; + } + return *this; +} + + void HTTPResponse::setStatus(HTTPStatus status) { _status = status; @@ -155,8 +175,8 @@ void HTTPResponse::setStatus(const std::string& status) { setStatus((HTTPStatus) NumberParser::parse(status)); } - - + + void HTTPResponse::setReason(const std::string& reason) { _reason = reason; @@ -169,7 +189,7 @@ void HTTPResponse::setStatusAndReason(HTTPStatus status, const std::string& reas _reason = reason; } - + void HTTPResponse::setStatusAndReason(HTTPStatus status) { setStatusAndReason(status, getReasonForStatus(status)); @@ -181,7 +201,7 @@ void HTTPResponse::setDate(const Poco::Timestamp& dateTime) set(DATE, DateTimeFormatter::format(dateTime, DateTimeFormat::HTTP_FORMAT)); } - + Poco::Timestamp HTTPResponse::getDate() const { const std::string& dateTime = get(DATE); @@ -225,7 +245,7 @@ void HTTPResponse::read(std::istream& istr) std::string version; std::string status; std::string reason; - + int ch = istr.get(); if (istr.bad()) throw NetException("Error reading HTTP response header"); if (ch == eof) throw NoMessageException(); @@ -255,25 +275,25 @@ const std::string& HTTPResponse::getReasonForStatus(HTTPStatus status) { switch (status) { - case HTTP_CONTINUE: + case HTTP_CONTINUE: return HTTP_REASON_CONTINUE; - case HTTP_SWITCHING_PROTOCOLS: + case HTTP_SWITCHING_PROTOCOLS: return HTTP_REASON_SWITCHING_PROTOCOLS; case HTTP_PROCESSING: return HTTP_REASON_PROCESSING; - case HTTP_OK: + case HTTP_OK: return HTTP_REASON_OK; - case HTTP_CREATED: + case HTTP_CREATED: return HTTP_REASON_CREATED; - case HTTP_ACCEPTED: + case HTTP_ACCEPTED: return HTTP_REASON_ACCEPTED; - case HTTP_NONAUTHORITATIVE: + case HTTP_NONAUTHORITATIVE: return HTTP_REASON_NONAUTHORITATIVE; - case HTTP_NO_CONTENT: + case HTTP_NO_CONTENT: return HTTP_REASON_NO_CONTENT; - case HTTP_RESET_CONTENT: + case HTTP_RESET_CONTENT: return HTTP_REASON_RESET_CONTENT; - case HTTP_PARTIAL_CONTENT: + case HTTP_PARTIAL_CONTENT: return HTTP_REASON_PARTIAL_CONTENT; case HTTP_MULTI_STATUS: return HTTP_REASON_MULTI_STATUS; @@ -281,58 +301,58 @@ const std::string& HTTPResponse::getReasonForStatus(HTTPStatus status) return HTTP_REASON_ALREADY_REPORTED; case HTTP_IM_USED: return HTTP_REASON_IM_USED; - case HTTP_MULTIPLE_CHOICES: + case HTTP_MULTIPLE_CHOICES: return HTTP_REASON_MULTIPLE_CHOICES; - case HTTP_MOVED_PERMANENTLY: + case HTTP_MOVED_PERMANENTLY: return HTTP_REASON_MOVED_PERMANENTLY; - case HTTP_FOUND: + case HTTP_FOUND: return HTTP_REASON_FOUND; - case HTTP_SEE_OTHER: + case HTTP_SEE_OTHER: return HTTP_REASON_SEE_OTHER; - case HTTP_NOT_MODIFIED: + case HTTP_NOT_MODIFIED: return HTTP_REASON_NOT_MODIFIED; - case HTTP_USE_PROXY: + case HTTP_USE_PROXY: return HTTP_REASON_USE_PROXY; - case HTTP_TEMPORARY_REDIRECT: + case HTTP_TEMPORARY_REDIRECT: return HTTP_REASON_TEMPORARY_REDIRECT; - case HTTP_BAD_REQUEST: + case HTTP_BAD_REQUEST: return HTTP_REASON_BAD_REQUEST; - case HTTP_UNAUTHORIZED: + case HTTP_UNAUTHORIZED: return HTTP_REASON_UNAUTHORIZED; - case HTTP_PAYMENT_REQUIRED: + case HTTP_PAYMENT_REQUIRED: return HTTP_REASON_PAYMENT_REQUIRED; - case HTTP_FORBIDDEN: + case HTTP_FORBIDDEN: return HTTP_REASON_FORBIDDEN; - case HTTP_NOT_FOUND: + case HTTP_NOT_FOUND: return HTTP_REASON_NOT_FOUND; case HTTP_METHOD_NOT_ALLOWED: return HTTP_REASON_METHOD_NOT_ALLOWED; - case HTTP_NOT_ACCEPTABLE: + case HTTP_NOT_ACCEPTABLE: return HTTP_REASON_NOT_ACCEPTABLE; - case HTTP_PROXY_AUTHENTICATION_REQUIRED: + case HTTP_PROXY_AUTHENTICATION_REQUIRED: return HTTP_REASON_PROXY_AUTHENTICATION_REQUIRED; - case HTTP_REQUEST_TIMEOUT: + case HTTP_REQUEST_TIMEOUT: return HTTP_REASON_REQUEST_TIMEOUT; - case HTTP_CONFLICT: + case HTTP_CONFLICT: return HTTP_REASON_CONFLICT; - case HTTP_GONE: + case HTTP_GONE: return HTTP_REASON_GONE; - case HTTP_LENGTH_REQUIRED: + case HTTP_LENGTH_REQUIRED: return HTTP_REASON_LENGTH_REQUIRED; - case HTTP_PRECONDITION_FAILED: + case HTTP_PRECONDITION_FAILED: return HTTP_REASON_PRECONDITION_FAILED; - case HTTP_REQUEST_ENTITY_TOO_LARGE: + case HTTP_REQUEST_ENTITY_TOO_LARGE: return HTTP_REASON_REQUEST_ENTITY_TOO_LARGE; - case HTTP_REQUEST_URI_TOO_LONG: + case HTTP_REQUEST_URI_TOO_LONG: return HTTP_REASON_REQUEST_URI_TOO_LONG; - case HTTP_UNSUPPORTED_MEDIA_TYPE: + case HTTP_UNSUPPORTED_MEDIA_TYPE: return HTTP_REASON_UNSUPPORTED_MEDIA_TYPE; - case HTTP_REQUESTED_RANGE_NOT_SATISFIABLE: + case HTTP_REQUESTED_RANGE_NOT_SATISFIABLE: return HTTP_REASON_REQUESTED_RANGE_NOT_SATISFIABLE; - case HTTP_EXPECTATION_FAILED: + case HTTP_EXPECTATION_FAILED: return HTTP_REASON_EXPECTATION_FAILED; case HTTP_IM_A_TEAPOT: - return HTTP_REASON_IM_A_TEAPOT; + return HTTP_REASON_IM_A_TEAPOT; case HTTP_ENCHANCE_YOUR_CALM: return HTTP_REASON_ENCHANCE_YOUR_CALM; case HTTP_MISDIRECTED_REQUEST: @@ -353,17 +373,17 @@ const std::string& HTTPResponse::getReasonForStatus(HTTPStatus status) return HTTP_REASON_REQUEST_HEADER_FIELDS_TOO_LARGE; case HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: return HTTP_REASON_UNAVAILABLE_FOR_LEGAL_REASONS; - case HTTP_INTERNAL_SERVER_ERROR: + case HTTP_INTERNAL_SERVER_ERROR: return HTTP_REASON_INTERNAL_SERVER_ERROR; - case HTTP_NOT_IMPLEMENTED: + case HTTP_NOT_IMPLEMENTED: return HTTP_REASON_NOT_IMPLEMENTED; - case HTTP_BAD_GATEWAY: + case HTTP_BAD_GATEWAY: return HTTP_REASON_BAD_GATEWAY; case HTTP_SERVICE_UNAVAILABLE: return HTTP_REASON_SERVICE_UNAVAILABLE; - case HTTP_GATEWAY_TIMEOUT: + case HTTP_GATEWAY_TIMEOUT: return HTTP_REASON_GATEWAY_TIMEOUT; - case HTTP_VERSION_NOT_SUPPORTED: + case HTTP_VERSION_NOT_SUPPORTED: return HTTP_REASON_VERSION_NOT_SUPPORTED; case HTTP_VARIANT_ALSO_NEGOTIATES: return HTTP_REASON_VARIANT_ALSO_NEGOTIATES; @@ -375,7 +395,7 @@ const std::string& HTTPResponse::getReasonForStatus(HTTPStatus status) return HTTP_REASON_NOT_EXTENDED; case HTTP_NETWORK_AUTHENTICATION_REQUIRED: return HTTP_REASON_NETWORK_AUTHENTICATION_REQUIRED; - default: + default: return HTTP_REASON_UNKNOWN; } } diff --git a/Net/src/NTLMCredentials.cpp b/Net/src/NTLMCredentials.cpp new file mode 100644 index 000000000..9fcfb5407 --- /dev/null +++ b/Net/src/NTLMCredentials.cpp @@ -0,0 +1,329 @@ +// +// NTLMCredentials.cpp +// +// Library: Net +// Package: HTTP +// Module: NTLMCredentials +// +// Copyright (c) 2019, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#include "Poco/Net/NTLMCredentials.h" +#include "Poco/HMACEngine.h" +#include "Poco/MD4Engine.h" +#include "Poco/MD5Engine.h" +#include "Poco/DigestStream.h" +#include "Poco/StreamCopier.h" +#include "Poco/UTF8Encoding.h" +#include "Poco/UTF16Encoding.h" +#include "Poco/TextConverter.h" +#include "Poco/UTF8String.h" +#include "Poco/Random.h" +#include "Poco/Timestamp.h" +#include "Poco/MemoryStream.h" +#include + + +namespace Poco { +namespace Net { + + +const std::string NTLMCredentials::NTLMSSP("NTLMSSP"); + + +std::vector NTLMCredentials::createNonce() +{ + Poco::MD5Engine md5; + Poco::Random rnd; + rnd.seed(); + + Poco::UInt32 n = rnd.next(); + md5.update(&n, sizeof(n)); + + Poco::Timestamp ts; + md5.update(&ts, sizeof(ts)); + + Poco::DigestEngine::Digest d = md5.digest(); + d.resize(8); + + return d; +} + + +Poco::UInt64 NTLMCredentials::createTimestamp() +{ + const Poco::UInt64 EPOCH_DELTA_SECONDS = 11644473600; // seconds between January 1, 1970 and January 1, 1601 + + Poco::Timestamp now; + Poco::UInt64 ts = now.epochMicroseconds(); + ts += EPOCH_DELTA_SECONDS*1000000; // since January 1, 1601 + ts *= 10; // tenths of a microsecond + return ts; +} + + +std::vector NTLMCredentials::createPasswordHash(const std::string& password) +{ + Poco::UTF8Encoding utf8; + Poco::UTF16Encoding utf16(Poco::UTF16Encoding::LITTLE_ENDIAN_BYTE_ORDER); + Poco::TextConverter converter(utf8, utf16); + + std::string utf16Password; + converter.convert(password, utf16Password); + + Poco::MD4Engine md4; + md4.update(utf16Password); + return md4.digest(); +} + + +std::vector NTLMCredentials::createNTLMv2Hash(const std::string& username, const std::string& target, const std::string& password) +{ + Poco::UTF8Encoding utf8; + Poco::UTF16Encoding utf16(Poco::UTF16Encoding::LITTLE_ENDIAN_BYTE_ORDER); + Poco::TextConverter converter(utf8, utf16); + + std::vector passwordHash = createPasswordHash(password); + + std::string userDomain = Poco::UTF8::toUpper(username); + userDomain += target; + + std::string utf16UserDomain; + converter.convert(userDomain, utf16UserDomain); + + std::string passwordHashString(reinterpret_cast(&passwordHash[0]), passwordHash.size()); + Poco::HMACEngine hmac(passwordHashString); + hmac.update(utf16UserDomain); + return hmac.digest(); +} + + +std::vector NTLMCredentials::createLMv2Response(const std::vector& ntlm2Hash, const std::vector& challenge, const std::vector& nonce) +{ + poco_assert (challenge.size() == 8); + poco_assert (nonce.size() == 8); + + std::vector lm2Response; + + std::string ntlm2HashString(reinterpret_cast(&ntlm2Hash[0]), ntlm2Hash.size()); + Poco::HMACEngine hmac2(ntlm2HashString); + hmac2.update(&challenge[0], challenge.size()); + hmac2.update(&nonce[0], nonce.size()); + lm2Response = hmac2.digest(); + lm2Response.insert(lm2Response.end(), nonce.begin(), nonce.end()); + + return lm2Response; +} + + +std::vector NTLMCredentials::createNTLMv2Response(const std::vector& ntlm2Hash, const std::vector& challenge, const std::vector& nonce, const std::vector& targetInfo, Poco::UInt64 timestamp) +{ + poco_assert (challenge.size() == 8); + poco_assert (nonce.size() == 8); + + std::vector blob; + blob.resize(28 + targetInfo.size() + 4 + 16); + + Poco::MemoryOutputStream blobStream(reinterpret_cast(&blob[16]), blob.size() - 16); + Poco::BinaryWriter writer(blobStream, Poco::BinaryWriter::LITTLE_ENDIAN_BYTE_ORDER); + writer << Poco::UInt32(0x0101); + writer << Poco::UInt32(0); + writer << timestamp; + writer.writeRaw(reinterpret_cast(&nonce[0]), nonce.size()); + writer << Poco::UInt32(0); + writer.writeRaw(reinterpret_cast(&targetInfo[0]), targetInfo.size()); + writer << Poco::UInt32(0); + + poco_assert_dbg (blobStream.charsWritten() == blob.size() - 16); + + std::string ntlm2HashString(reinterpret_cast(&ntlm2Hash[0]), ntlm2Hash.size()); + Poco::HMACEngine hmac2(ntlm2HashString); + hmac2.update(&challenge[0], challenge.size()); + hmac2.update(&blob[16], blob.size() - 16); + Poco::DigestEngine::Digest d = hmac2.digest(); + + poco_assert_dbg (d.size() == 16); + + std::memcpy(&blob[0], &d[0], 16); + + return blob; +} + + +std::vector NTLMCredentials::formatNegotiateMessage(const NegotiateMessage& message) +{ + Poco::UTF8Encoding utf8; + Poco::UTF16Encoding utf16(Poco::UTF16Encoding::LITTLE_ENDIAN_BYTE_ORDER); + Poco::TextConverter converter(utf8, utf16); + + std::string utf16Domain; + converter.convert(message.domain, utf16Domain); + + std::string utf16Workstation; + converter.convert(message.workstation, utf16Workstation); + + std::size_t size = 8 // signature + + 4 // type + + 4 // flags + + 8 + utf16Domain.size() + + 8 + utf16Workstation.size(); + + Poco::UInt32 flags = message.flags | NTLM_FLAG_NEGOTIATE_UNICODE | NTLM_FLAG_REQUEST_TARGET | NTLM_FLAG_NEGOTIATE_NTLM | NTLM_FLAG_NEGOTIATE_NTLM2_KEY | NTLM_FLAG_NEGOTIATE_ALWAYS_SIGN; + if (!utf16Domain.empty()) flags |= NTLM_FLAG_DOMAIN_SUPPLIED; + if (!utf16Workstation.empty()) flags |= NTLM_FLAG_WORKST_SUPPLIED; + + BufferDesc domainDesc(utf16Domain.size(), 8 + 4 + 4 + 8); + BufferDesc workstDesc(utf16Workstation.size(), domainDesc.offset + domainDesc.length); + + std::vector buffer(size); + Poco::MemoryOutputStream bufferStream(reinterpret_cast(&buffer[0]), buffer.size()); + Poco::BinaryWriter writer(bufferStream, Poco::BinaryWriter::LITTLE_ENDIAN_BYTE_ORDER); + writer.writeRaw(NTLMSSP.c_str(), 8); + writer << Poco::UInt32(NTLM_MESSAGE_TYPE_NEGOTIATE); + writer << flags; + writeBufferDesc(writer, domainDesc); + writeBufferDesc(writer, workstDesc); + writer.writeRaw(utf16Domain); + writer.writeRaw(utf16Workstation); + + return buffer; +} + + +bool NTLMCredentials::parseChallengeMessage(const unsigned char* buffer, std::size_t size, ChallengeMessage& message) +{ + Poco::MemoryInputStream istr(reinterpret_cast(buffer), size); + Poco::BinaryReader reader(istr, Poco::BinaryReader::LITTLE_ENDIAN_BYTE_ORDER); + + std::string signature; + reader.readRaw(7, signature); + if (signature != NTLMSSP) return false; + + Poco::UInt8 zero; + reader >> zero; + if (zero != 0) return false; + + Poco::UInt32 type; + reader >> type; + if (type != NTLM_MESSAGE_TYPE_CHALLENGE) return false; + + BufferDesc targetDesc; + readBufferDesc(reader, targetDesc); + if (targetDesc.offset + targetDesc.length > size) return false; + + reader >> message.flags; + + message.challenge.resize(8); + reader.readRaw(reinterpret_cast(&message.challenge[0]), 8); + + if (message.flags & NTLM_FLAG_NEGOTIATE_TARGET) + { + Poco::UInt64 reserved; + reader >> reserved; + } + + BufferDesc targetInfoDesc; + if (message.flags & NTLM_FLAG_NEGOTIATE_TARGET) + { + readBufferDesc(reader, targetInfoDesc); + if (targetInfoDesc.offset + targetInfoDesc.length > size) return false; + } + + if (targetDesc.length > 0) + { + if (message.flags & NTLM_FLAG_NEGOTIATE_UNICODE) + { + Poco::UTF16Encoding utf16(Poco::UTF16Encoding::LITTLE_ENDIAN_BYTE_ORDER); + Poco::UTF8Encoding utf8; + Poco::TextConverter converter(utf16, utf8); + converter.convert(buffer + targetDesc.offset, targetDesc.length, message.target); + if (targetDesc.reserved == 0) message.target.resize(std::strlen(message.target.c_str())); + } + else + { + message.target.assign(buffer + targetDesc.offset, buffer + targetDesc.offset + targetDesc.length); + } + } + + if (targetInfoDesc.length > 0) + { + message.targetInfo.assign(buffer + targetInfoDesc.offset, buffer + targetInfoDesc.offset + targetInfoDesc.length); + } + + return true; +} + + +std::vector NTLMCredentials::formatAuthenticateMessage(const AuthenticateMessage& message) +{ + Poco::UTF8Encoding utf8; + Poco::UTF16Encoding utf16(Poco::UTF16Encoding::LITTLE_ENDIAN_BYTE_ORDER); + Poco::TextConverter converter(utf8, utf16); + + std::string utf16Target; + converter.convert(message.target, utf16Target); + + std::string utf16Username; + converter.convert(message.username, utf16Username); + + std::string utf16Workstation; + converter.convert(message.workstation, utf16Workstation); + + std::size_t size = 8 // signature + + 4 // type + + 8 + message.lmResponse.size() + + 8 + message.ntlmResponse.size() + + 8 + utf16Target.size() + + 8 + utf16Username.size() + + 8 + utf16Workstation.size() + + 8 // session key + + 4; // flags + + Poco::UInt32 flags = message.flags | NTLM_FLAG_NEGOTIATE_UNICODE; + + BufferDesc lmDesc(message.lmResponse.size(), 64); + BufferDesc ntlmDesc(message.ntlmResponse.size(), lmDesc.offset + lmDesc.length); + BufferDesc targetDesc(utf16Target.size(), ntlmDesc.offset + ntlmDesc.length); + BufferDesc usernameDesc(utf16Username.size(), targetDesc.offset + targetDesc.length); + BufferDesc workstDesc(utf16Workstation.size(), usernameDesc.offset + usernameDesc.length); + BufferDesc sessionKeyDesc(0, workstDesc.offset + workstDesc.length); + + std::vector buffer(size); + Poco::MemoryOutputStream bufferStream(reinterpret_cast(&buffer[0]), buffer.size()); + Poco::BinaryWriter writer(bufferStream, Poco::BinaryWriter::LITTLE_ENDIAN_BYTE_ORDER); + writer.writeRaw(NTLMSSP.c_str(), 8); + writer << Poco::UInt32(NTLM_MESSAGE_TYPE_AUTHENTICATE); + writeBufferDesc(writer, lmDesc); + writeBufferDesc(writer, ntlmDesc); + writeBufferDesc(writer, targetDesc); + writeBufferDesc(writer, usernameDesc); + writeBufferDesc(writer, workstDesc); + writeBufferDesc(writer, sessionKeyDesc); + writer << flags; + writer.writeRaw(reinterpret_cast(&message.lmResponse[0]), message.lmResponse.size()); + writer.writeRaw(reinterpret_cast(&message.ntlmResponse[0]), message.ntlmResponse.size()); + writer.writeRaw(utf16Target); + writer.writeRaw(utf16Username); + writer.writeRaw(utf16Workstation); + + return buffer; +} + + +void NTLMCredentials::readBufferDesc(Poco::BinaryReader& reader, BufferDesc& desc) +{ + reader >> desc.length >> desc.reserved >> desc.offset; +} + + +void NTLMCredentials::writeBufferDesc(Poco::BinaryWriter& writer, const BufferDesc& desc) +{ + writer << desc.length << desc.reserved << desc.offset; +} + + +} } // namespace Poco::Net diff --git a/Net/testsuite/Makefile b/Net/testsuite/Makefile index 96b6e39fb..d55ae9050 100644 --- a/Net/testsuite/Makefile +++ b/Net/testsuite/Makefile @@ -27,7 +27,8 @@ objects = \ WebSocketTest WebSocketTestSuite \ SyslogTest \ OAuth10CredentialsTest OAuth20CredentialsTest OAuthTestSuite \ - PollSetTest UDPServerTest UDPServerTestSuite + PollSetTest UDPServerTest UDPServerTestSuite \ + NTLMCredentialsTest target = testrunner target_version = 1 diff --git a/Net/testsuite/src/HTTPCredentialsTest.cpp b/Net/testsuite/src/HTTPCredentialsTest.cpp index ba4022c53..f9738b768 100644 --- a/Net/testsuite/src/HTTPCredentialsTest.cpp +++ b/Net/testsuite/src/HTTPCredentialsTest.cpp @@ -44,7 +44,7 @@ void HTTPCredentialsTest::testBasicCredentials() { HTTPRequest request; assertTrue (!request.hasCredentials()); - + HTTPBasicCredentials cred("user", "secret"); cred.authenticate(request); assertTrue (request.hasCredentials()); @@ -53,7 +53,7 @@ void HTTPCredentialsTest::testBasicCredentials() request.getCredentials(scheme, info); assertTrue (scheme == "Basic"); assertTrue (info == "dXNlcjpzZWNyZXQ="); - + HTTPBasicCredentials cred2(request); assertTrue (cred2.getUsername() == "user"); assertTrue (cred2.getPassword() == "secret"); @@ -64,7 +64,7 @@ void HTTPCredentialsTest::testProxyBasicCredentials() { HTTPRequest request; assertTrue (!request.hasProxyCredentials()); - + HTTPBasicCredentials cred("user", "secret"); cred.proxyAuthenticate(request); assertTrue (request.hasProxyCredentials()); @@ -79,7 +79,7 @@ void HTTPCredentialsTest::testProxyBasicCredentials() void HTTPCredentialsTest::testBadCredentials() { HTTPRequest request; - + std::string scheme; std::string info; try @@ -90,12 +90,12 @@ void HTTPCredentialsTest::testBadCredentials() catch (NotAuthenticatedException&) { } - + request.setCredentials("Test", "SomeData"); request.getCredentials(scheme, info); assertTrue (scheme == "Test"); assertTrue (info == "SomeData"); - + try { HTTPBasicCredentials cred(request); @@ -111,7 +111,7 @@ void HTTPCredentialsTest::testAuthenticationParams() { const std::string authInfo("nonce=\"212573bb90170538efad012978ab811f%lu\", realm=\"TestDigest\", response=\"40e4889cfbd0e561f71e3107a2863bc4\", uri=\"/digest/\", username=\"user\""); HTTPAuthenticationParams params(authInfo); - + assertTrue (params["nonce"] == "212573bb90170538efad012978ab811f%lu"); assertTrue (params["realm"] == "TestDigest"); assertTrue (params["response"] == "40e4889cfbd0e561f71e3107a2863bc4"); @@ -119,7 +119,7 @@ void HTTPCredentialsTest::testAuthenticationParams() assertTrue (params["username"] == "user"); assertTrue (params.size() == 5); assertTrue (params.toString() == authInfo); - + params.clear(); HTTPRequest request; request.set("Authorization", "Digest " + authInfo); @@ -134,22 +134,29 @@ void HTTPCredentialsTest::testAuthenticationParams() params.clear(); HTTPResponse response; - response.set("WWW-Authenticate", "Digest realm=\"TestDigest\", nonce=\"212573bb90170538efad012978ab811f%lu\""); + response.set("WWW-Authenticate", "Digest realm=\"TestDigest\", nonce=\"212573bb90170538efad012978ab811f%lu\""); params.fromResponse(response); - + assertTrue (params["realm"] == "TestDigest"); assertTrue (params["nonce"] == "212573bb90170538efad012978ab811f%lu"); assertTrue (params.size() == 2); + + params.clear(); + response.set("WWW-Authenticate", "NTLM TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA"); + params.fromResponse(response); + + assertTrue (params["NTLM"] == "TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA"); + assertTrue (params.size() == 1); } void HTTPCredentialsTest::testAuthenticationParamsMultipleHeaders() { HTTPResponse response; - response.add("WWW-Authenticate", "Unsupported realm=\"TestUnsupported\""); - response.add("WWW-Authenticate", "Digest realm=\"TestDigest\", nonce=\"212573bb90170538efad012978ab811f%lu\""); + response.add("WWW-Authenticate", "Unsupported realm=\"TestUnsupported\""); + response.add("WWW-Authenticate", "Digest realm=\"TestDigest\", nonce=\"212573bb90170538efad012978ab811f%lu\""); HTTPAuthenticationParams params(response); - + assertTrue (params["realm"] == "TestDigest"); assertTrue (params["nonce"] == "212573bb90170538efad012978ab811f%lu"); assertTrue (params.size() == 2); @@ -161,7 +168,7 @@ void HTTPCredentialsTest::testDigestCredentials() HTTPDigestCredentials creds("user", "s3cr3t"); HTTPRequest request(HTTPRequest::HTTP_GET, "/digest/"); HTTPResponse response; - response.set("WWW-Authenticate", "Digest realm=\"TestDigest\", nonce=\"212573bb90170538efad012978ab811f%lu\""); + response.set("WWW-Authenticate", "Digest realm=\"TestDigest\", nonce=\"212573bb90170538efad012978ab811f%lu\""); creds.authenticate(request, response); std::string auth = request.get("Authorization"); assertTrue (auth == "Digest username=\"user\", nonce=\"212573bb90170538efad012978ab811f%lu\", realm=\"TestDigest\", uri=\"/digest/\", response=\"40e4889cfbd0e561f71e3107a2863bc4\""); @@ -173,9 +180,9 @@ void HTTPCredentialsTest::testDigestCredentialsQoP() HTTPDigestCredentials creds("user", "s3cr3t"); HTTPRequest request(HTTPRequest::HTTP_GET, "/digest/"); HTTPResponse response; - response.set("WWW-Authenticate", "Digest realm=\"TestDigest\", nonce=\"212573bb90170538efad012978ab811f%lu\", opaque=\"opaque\", qop=\"auth,auth-int\""); + response.set("WWW-Authenticate", "Digest realm=\"TestDigest\", nonce=\"212573bb90170538efad012978ab811f%lu\", opaque=\"opaque\", qop=\"auth,auth-int\""); creds.authenticate(request, response); - + HTTPAuthenticationParams params(request); assertTrue (params["nonce"] == "212573bb90170538efad012978ab811f%lu"); assertTrue (params["realm"] == "TestDigest"); @@ -187,12 +194,12 @@ void HTTPCredentialsTest::testDigestCredentialsQoP() assertTrue (params["nc"] == "00000001"); assertTrue (params["qop"] == "auth"); assertTrue (params.size() == 9); - + std::string cnonce = params["cnonce"]; std::string aresp = params["response"]; - + params.clear(); - + creds.updateAuthInfo(request); params.fromRequest(request); assertTrue (params["nonce"] == "212573bb90170538efad012978ab811f%lu"); @@ -213,8 +220,8 @@ void HTTPCredentialsTest::testCredentialsBasic() HTTPCredentials creds("user", "s3cr3t"); HTTPRequest request(HTTPRequest::HTTP_GET, "/basic/"); HTTPResponse response; - response.set("WWW-Authenticate", "Basic realm=\"TestBasic\""); - creds.authenticate(request, response); + response.set("WWW-Authenticate", "Basic realm=\"TestBasic\""); + creds.authenticate(request, response); assertTrue (request.get("Authorization") == "Basic dXNlcjpzM2NyM3Q="); } @@ -224,8 +231,8 @@ void HTTPCredentialsTest::testProxyCredentialsBasic() HTTPCredentials creds("user", "s3cr3t"); HTTPRequest request(HTTPRequest::HTTP_GET, "/basic/"); HTTPResponse response; - response.set("Proxy-Authenticate", "Basic realm=\"TestBasic\""); - creds.proxyAuthenticate(request, response); + response.set("Proxy-Authenticate", "Basic realm=\"TestBasic\""); + creds.proxyAuthenticate(request, response); assertTrue (request.get("Proxy-Authorization") == "Basic dXNlcjpzM2NyM3Q="); } @@ -235,7 +242,7 @@ void HTTPCredentialsTest::testCredentialsDigest() HTTPCredentials creds("user", "s3cr3t"); HTTPRequest request(HTTPRequest::HTTP_GET, "/digest/"); HTTPResponse response; - response.set("WWW-Authenticate", "Digest realm=\"TestDigest\", nonce=\"212573bb90170538efad012978ab811f%lu\""); + response.set("WWW-Authenticate", "Digest realm=\"TestDigest\", nonce=\"212573bb90170538efad012978ab811f%lu\""); creds.authenticate(request, response); std::string auth = request.get("Authorization"); assertTrue (auth == "Digest username=\"user\", nonce=\"212573bb90170538efad012978ab811f%lu\", realm=\"TestDigest\", uri=\"/digest/\", response=\"40e4889cfbd0e561f71e3107a2863bc4\""); @@ -247,8 +254,8 @@ void HTTPCredentialsTest::testCredentialsDigestMultipleHeaders() HTTPCredentials creds("user", "s3cr3t"); HTTPRequest request(HTTPRequest::HTTP_GET, "/digest/"); HTTPResponse response; - response.add("WWW-Authenticate", "Unsupported realm=\"TestUnsupported\""); - response.add("WWW-Authenticate", "Digest realm=\"TestDigest\", nonce=\"212573bb90170538efad012978ab811f%lu\""); + response.add("WWW-Authenticate", "Unsupported realm=\"TestUnsupported\""); + response.add("WWW-Authenticate", "Digest realm=\"TestDigest\", nonce=\"212573bb90170538efad012978ab811f%lu\""); creds.authenticate(request, response); std::string auth = request.get("Authorization"); assertTrue (auth == "Digest username=\"user\", nonce=\"212573bb90170538efad012978ab811f%lu\", realm=\"TestDigest\", uri=\"/digest/\", response=\"40e4889cfbd0e561f71e3107a2863bc4\""); @@ -260,8 +267,8 @@ void HTTPCredentialsTest::testProxyCredentialsDigest() HTTPCredentials creds("user", "s3cr3t"); HTTPRequest request(HTTPRequest::HTTP_GET, "/digest/"); HTTPResponse response; - response.set("Proxy-Authenticate", "Digest realm=\"TestDigest\", nonce=\"212573bb90170538efad012978ab811f%lu\""); - creds.proxyAuthenticate(request, response); + response.set("Proxy-Authenticate", "Digest realm=\"TestDigest\", nonce=\"212573bb90170538efad012978ab811f%lu\""); + creds.proxyAuthenticate(request, response); assertTrue (request.get("Proxy-Authorization") == "Digest username=\"user\", nonce=\"212573bb90170538efad012978ab811f%lu\", realm=\"TestDigest\", uri=\"/digest/\", response=\"40e4889cfbd0e561f71e3107a2863bc4\""); } @@ -282,10 +289,10 @@ void HTTPCredentialsTest::testVerifyAuthInfo() HTTPDigestCredentials creds("user", "s3cr3t"); HTTPRequest request(HTTPRequest::HTTP_GET, "/digest/"); HTTPResponse response; - response.set("WWW-Authenticate", "Digest realm=\"TestDigest\", nonce=\"212573bb90170538efad012978ab811f%lu\""); + response.set("WWW-Authenticate", "Digest realm=\"TestDigest\", nonce=\"212573bb90170538efad012978ab811f%lu\""); creds.authenticate(request, response); assertTrue (creds.verifyAuthInfo(request)); - + request.set("Authorization", "Digest nonce=\"212573bb90170538efad012978ab811f%lu\", realm=\"TestDigest\", response=\"xxe4889cfbd0e561f71e3107a2863bc4\", uri=\"/digest/\", username=\"user\""); assertTrue (!creds.verifyAuthInfo(request)); } @@ -296,10 +303,10 @@ void HTTPCredentialsTest::testVerifyAuthInfoQoP() HTTPDigestCredentials creds("user", "s3cr3t"); HTTPRequest request(HTTPRequest::HTTP_GET, "/digest/"); HTTPResponse response; - response.set("WWW-Authenticate", "Digest realm=\"TestDigest\", nonce=\"212573bb90170538efad012978ab811f%lu\", opaque=\"opaque\", qop=\"auth,auth-int\""); + response.set("WWW-Authenticate", "Digest realm=\"TestDigest\", nonce=\"212573bb90170538efad012978ab811f%lu\", opaque=\"opaque\", qop=\"auth,auth-int\""); creds.authenticate(request, response); assertTrue (creds.verifyAuthInfo(request)); - + request.set("Authorization", "Digest cnonce=\"f9c80ffd1c3bc4ee47ed92b704ba75a4\", nc=00000001, nonce=\"212573bb90170538efad012978ab811f%lu\", opaque=\"opaque\", qop=\"auth\", realm=\"TestDigest\", response=\"ff0e90b9aa019120ea0ed6e23ce95d9a\", uri=\"/digest/\", username=\"user\""); assertTrue (!creds.verifyAuthInfo(request)); } diff --git a/Net/testsuite/src/HTTPTestSuite.cpp b/Net/testsuite/src/HTTPTestSuite.cpp index 207e37b47..45ea77847 100644 --- a/Net/testsuite/src/HTTPTestSuite.cpp +++ b/Net/testsuite/src/HTTPTestSuite.cpp @@ -13,6 +13,7 @@ #include "HTTPResponseTest.h" #include "HTTPCookieTest.h" #include "HTTPCredentialsTest.h" +#include "NTLMCredentialsTest.h" CppUnit::Test* HTTPTestSuite::suite() @@ -23,6 +24,7 @@ CppUnit::Test* HTTPTestSuite::suite() pSuite->addTest(HTTPResponseTest::suite()); pSuite->addTest(HTTPCookieTest::suite()); pSuite->addTest(HTTPCredentialsTest::suite()); + pSuite->addTest(NTLMCredentialsTest::suite()); return pSuite; } diff --git a/Net/testsuite/src/NTLMCredentialsTest.cpp b/Net/testsuite/src/NTLMCredentialsTest.cpp new file mode 100644 index 000000000..742925a7d --- /dev/null +++ b/Net/testsuite/src/NTLMCredentialsTest.cpp @@ -0,0 +1,279 @@ +// +// NTLMCredentialsTest.cpp +// +// Copyright (c) 2014, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#include "NTLMCredentialsTest.h" +#include "CppUnit/TestCaller.h" +#include "CppUnit/TestSuite.h" +#include "Poco/Net/NTLMCredentials.h" +#include "Poco/DigestEngine.h" + + +using Poco::Net::NTLMCredentials; + + +NTLMCredentialsTest::NTLMCredentialsTest(const std::string& name): CppUnit::TestCase(name) +{ +} + + +NTLMCredentialsTest::~NTLMCredentialsTest() +{ +} + + +void NTLMCredentialsTest::testNonce() +{ + std::vector nonce1 = NTLMCredentials::createNonce(); + assertTrue (nonce1.size() == 8); + + std::vector nonce2 = NTLMCredentials::createNonce(); + assertTrue (nonce2.size() == 8); + + assertTrue (nonce1 != nonce2); +} + + +void NTLMCredentialsTest::testPasswordHash() +{ + std::vector passHash = NTLMCredentials::createPasswordHash("SecREt01"); + std::string passHashHex = Poco::DigestEngine::digestToHex(passHash); + assertTrue (passHashHex == "cd06ca7c7e10c99b1d33b7485a2ed808"); +} + + +void NTLMCredentialsTest::testNTLMv2Hash() +{ + std::vector ntlm2Hash = NTLMCredentials::createNTLMv2Hash("user", "DOMAIN", "SecREt01"); + std::string ntlm2HashHex = Poco::DigestEngine::digestToHex(ntlm2Hash); + assertTrue (ntlm2HashHex == "04b8e0ba74289cc540826bab1dee63ae"); +} + + +void NTLMCredentialsTest::testLMv2Response() +{ + static const unsigned char CHALLENGE[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}; + static const unsigned char NONCE[] = {0xff, 0xff, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44}; + + std::vector challenge(CHALLENGE, CHALLENGE + 8); + std::vector nonce(NONCE, NONCE + 8); + + std::vector ntlm2Hash = NTLMCredentials::createNTLMv2Hash("user", "DOMAIN", "SecREt01"); + std::vector lm2Response = NTLMCredentials::createLMv2Response(ntlm2Hash, challenge, nonce); + + std::string lm2ResponseHex = Poco::DigestEngine::digestToHex(lm2Response); + assertTrue (lm2ResponseHex == "d6e6152ea25d03b7c6ba6629c2d6aaf0ffffff0011223344"); +} + + +void NTLMCredentialsTest::testNTLMv2Response() +{ + static const unsigned char CHALLENGE[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}; + static const unsigned char NONCE[] = {0xff, 0xff, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44}; + static const unsigned char TARGET_INFO[] = { + 0x02, 0x00, 0x0c, 0x00, 0x44, 0x00, 0x4f, 0x00, + 0x4d, 0x00, 0x41, 0x00, 0x49, 0x00, 0x4e, 0x00, + 0x01, 0x00, 0x0c, 0x00, 0x53, 0x00, 0x45, 0x00, + 0x52, 0x00, 0x56, 0x00, 0x45, 0x00, 0x52, 0x00, + 0x04, 0x00, 0x14, 0x00, 0x64, 0x00, 0x6f, 0x00, + 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, + 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, + 0x03, 0x00, 0x22, 0x00, 0x73, 0x00, 0x65, 0x00, + 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, + 0x2e, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x6d, 0x00, + 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x2e, 0x00, + 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x00, 0x00, + 0x00, 0x00 + }; + + std::vector challenge(CHALLENGE, CHALLENGE + 8); + std::vector nonce(NONCE, NONCE + 8); + std::vector targetInfo(TARGET_INFO, TARGET_INFO + sizeof(TARGET_INFO)); + + std::vector ntlm2Hash = NTLMCredentials::createNTLMv2Hash("user", "DOMAIN", "SecREt01"); + Poco::UInt64 timestamp = Poco::UInt64(12700317600)*10000000; + + std::vector ntlm2Response = NTLMCredentials::createNTLMv2Response( + ntlm2Hash, + challenge, + nonce, + targetInfo, + timestamp); + + std::string ntlm2ResponseHex = Poco::DigestEngine::digestToHex(ntlm2Response); + + assertTrue (ntlm2ResponseHex == + "cbabbca713eb795d04c97abc01ee4983" + "01010000000000000090d336b734c301" + "ffffff00112233440000000002000c00" + "44004f004d00410049004e0001000c00" + "53004500520056004500520004001400" + "64006f006d00610069006e002e006300" + "6f006d00030022007300650072007600" + "650072002e0064006f006d0061006900" + "6e002e0063006f006d00000000000000" + "0000" + ); +} + + +void NTLMCredentialsTest::testFormatNegotiateMessage() +{ + NTLMCredentials::NegotiateMessage msg1; + msg1.flags = 0; + + std::vector msg1Buffer = NTLMCredentials::formatNegotiateMessage(msg1); + std::string msg1BufferHex = Poco::DigestEngine::digestToHex(msg1Buffer); + + assertTrue (msg1BufferHex == "4e544c4d53535000010000000582080000000000180000000000000018000000"); + + msg1.domain = "DOMAIN"; + msg1Buffer = NTLMCredentials::formatNegotiateMessage(msg1); + msg1BufferHex = Poco::DigestEngine::digestToHex(msg1Buffer); + + assertTrue (msg1BufferHex == "4e544c4d5353500001000000059208000c000c0018000000000000002400000044004f004d00410049004e00"); + + msg1.workstation = "WORKST"; + msg1Buffer = NTLMCredentials::formatNegotiateMessage(msg1); + msg1BufferHex = Poco::DigestEngine::digestToHex(msg1Buffer); + + assertTrue (msg1BufferHex == "4e544c4d535350000100000005b208000c000c00180000000c000c002400000044004f004d00410049004e0057004f0052004b0053005400"); +} + + +void NTLMCredentialsTest::testParseChallengeMessage() +{ + const unsigned char BUFFER[] = { + 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef + }; + + NTLMCredentials::ChallengeMessage msg2; + bool ok = NTLMCredentials::parseChallengeMessage(BUFFER, sizeof(BUFFER), msg2); + + assertTrue (ok); + assertTrue (msg2.flags == (NTLMCredentials::NTLM_FLAG_NEGOTIATE_OEM | NTLMCredentials::NTLM_FLAG_NEGOTIATE_NTLM)); + assertTrue (msg2.challenge.size() == 8); + assertTrue (msg2.targetInfo.empty()); + + assertTrue (msg2.challenge[0] == 0x01); + assertTrue (msg2.challenge[1] == 0x23); + assertTrue (msg2.challenge[7] == 0xef); +} + + +void NTLMCredentialsTest::testParseChallengeMessageWithTargetInfo() +{ + const unsigned char BUFFER[] = { + 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x01, 0x02, 0x81, 0x00, + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x62, 0x00, 0x62, 0x00, 0x3c, 0x00, 0x00, 0x00, + 0x44, 0x00, 0x4f, 0x00, 0x4d, 0x00, 0x41, 0x00, + 0x49, 0x00, 0x4e, 0x00, 0x02, 0x00, 0x0c, 0x00, + 0x44, 0x00, 0x4f, 0x00, 0x4d, 0x00, 0x41, 0x00, + 0x49, 0x00, 0x4e, 0x00, 0x01, 0x00, 0x0c, 0x00, + 0x53, 0x00, 0x45, 0x00, 0x52, 0x00, 0x56, 0x00, + 0x45, 0x00, 0x52, 0x00, 0x04, 0x00, 0x14, 0x00, + 0x64, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00, + 0x69, 0x00, 0x6e, 0x00, 0x2e, 0x00, 0x63, 0x00, + 0x6f, 0x00, 0x6d, 0x00, 0x03, 0x00, 0x22, 0x00, + 0x73, 0x00, 0x65, 0x00, 0x72, 0x00, 0x76, 0x00, + 0x65, 0x00, 0x72, 0x00, 0x2e, 0x00, 0x64, 0x00, + 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, + 0x6e, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, + 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + NTLMCredentials::ChallengeMessage msg2; + bool ok = NTLMCredentials::parseChallengeMessage(BUFFER, sizeof(BUFFER), msg2); + + assertTrue (ok); + assertTrue (msg2.flags == (NTLMCredentials::NTLM_FLAG_NEGOTIATE_UNICODE | NTLMCredentials::NTLM_FLAG_NEGOTIATE_NTLM | NTLMCredentials::NTLM_FLAG_NEGOTIATE_TARGET | NTLMCredentials::NTLM_FLAG_TARGET_DOMAIN)); + assertTrue (msg2.challenge.size() == 8); + assertTrue (msg2.target == "DOMAIN"); + + assertTrue (msg2.targetInfo.size() == 98); + + assertTrue (msg2.challenge[0] == 0x01); + assertTrue (msg2.challenge[1] == 0x23); + assertTrue (msg2.challenge[7] == 0xef); + + assertTrue (msg2.targetInfo[0] == 0x02); + assertTrue (msg2.targetInfo[1] == 0x00); + assertTrue (msg2.targetInfo[97] == 0x00); +} + + +void NTLMCredentialsTest::testFormatAuthenticateMessage() +{ + const unsigned char LM[] = { + 0xc3, 0x37, 0xcd, 0x5c, 0xbd, 0x44, 0xfc, 0x97, + 0x82, 0xa6, 0x67, 0xaf, 0x6d, 0x42, 0x7c, 0x6d, + 0xe6, 0x7c, 0x20, 0xc2, 0xd3, 0xe7, 0x7c, 0x56 + }; + const unsigned char NTLM[] = { + 0x25, 0xa9, 0x8c, 0x1c, 0x31, 0xe8, 0x18, 0x47, + 0x46, 0x6b, 0x29, 0xb2, 0xdf, 0x46, 0x80, 0xf3, + 0x99, 0x58, 0xfb, 0x8c, 0x21, 0x3a, 0x9c, 0xc6 + }; + + NTLMCredentials::AuthenticateMessage msg3; + msg3.flags = NTLMCredentials::NTLM_FLAG_NEGOTIATE_UNICODE | NTLMCredentials::NTLM_FLAG_NEGOTIATE_NTLM; + msg3.target = "DOMAIN"; + msg3.username = "user"; + msg3.workstation = "WORKSTATION"; + msg3.lmResponse.assign(LM, LM + sizeof(LM)); + msg3.ntlmResponse.assign(NTLM, NTLM + sizeof(NTLM)); + + std::vector msg3Buffer = NTLMCredentials::formatAuthenticateMessage(msg3); + std::string msg3BufferHex = Poco::DigestEngine::digestToHex(msg3Buffer); + + assertTrue (msg3BufferHex == + "4e544c4d5353500003000000180018004000000018001800" + "580000000c000c0070000000080008007c00000016001600" + "84000000000000009a00000001020000c337cd5cbd44fc97" + "82a667af6d427c6de67c20c2d3e77c5625a98c1c31e81847" + "466b29b2df4680f39958fb8c213a9cc644004f004d004100" + "49004e00750073006500720057004f0052004b0053005400" + "4100540049004f004e00" + ); +} + + +void NTLMCredentialsTest::setUp() +{ +} + + +void NTLMCredentialsTest::tearDown() +{ +} + + +CppUnit::Test* NTLMCredentialsTest::suite() +{ + CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("NTLMCredentialsTest"); + + CppUnit_addTest(pSuite, NTLMCredentialsTest, testNonce); + CppUnit_addTest(pSuite, NTLMCredentialsTest, testPasswordHash); + CppUnit_addTest(pSuite, NTLMCredentialsTest, testNTLMv2Hash); + CppUnit_addTest(pSuite, NTLMCredentialsTest, testLMv2Response); + CppUnit_addTest(pSuite, NTLMCredentialsTest, testNTLMv2Response); + CppUnit_addTest(pSuite, NTLMCredentialsTest, testFormatNegotiateMessage); + CppUnit_addTest(pSuite, NTLMCredentialsTest, testParseChallengeMessage); + CppUnit_addTest(pSuite, NTLMCredentialsTest, testParseChallengeMessageWithTargetInfo); + CppUnit_addTest(pSuite, NTLMCredentialsTest, testFormatAuthenticateMessage); + + return pSuite; +} diff --git a/Net/testsuite/src/NTLMCredentialsTest.h b/Net/testsuite/src/NTLMCredentialsTest.h new file mode 100644 index 000000000..b7292335a --- /dev/null +++ b/Net/testsuite/src/NTLMCredentialsTest.h @@ -0,0 +1,46 @@ +// +// NTLMCredentialsTest.h +// +// Definition of the NTLMCredentialsTest class. +// +// Copyright (c) 2019, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#ifndef NTLMCredentialsTest_INCLUDED +#define NTLMCredentialsTest_INCLUDED + + +#include "Poco/Net/Net.h" +#include "CppUnit/TestCase.h" + + +class NTLMCredentialsTest: public CppUnit::TestCase +{ +public: + NTLMCredentialsTest(const std::string& name); + ~NTLMCredentialsTest(); + + void testNonce(); + void testPasswordHash(); + void testNTLMv2Hash(); + void testLMv2Response(); + void testNTLMv2Response(); + void testFormatNegotiateMessage(); + void testParseChallengeMessage(); + void testParseChallengeMessageWithTargetInfo(); + void testFormatAuthenticateMessage(); + + void setUp(); + void tearDown(); + + static CppUnit::Test* suite(); + +private: +}; + + +#endif // NTLMCredentialsTest_INCLUDED