public final class DigestChallengeResponse
extends java.lang.Object
Authorization
header.
Instances of this class is normally created as a response to an incoming challenge using
responseTo(DigestChallenge)
. To generate the Authorization
header, som
additional values must be set:
username
and password
for
authentication.digest-uri
used in the HTTP request.request method
of the request, such as "GET" or "POST".
Here is an example of how to create a response:
DigestChallengeResponse response = DigestChallengeResponse.responseTo(challenge)
.username("user")
.password("passwd")
.digestUri("/example")
.requestMethod("GET");
Each time the challenge response is reused the nonce count must be increased by one, see
incrementNonceCount()
. It is also a good idea to generate a new random client nonce with
randomizeClientNonce()
:
response.incrementNonceCount().randomizeClientNonce(); // Response is now ready for reuse
auth-int
quality of protection (optional, rarely used)auth-int
quality of protection the challenge response includes a hash of the
request's entity-body
, which provides some protection from man-in-the-middle attacks.
Not all requests include an entity-body
, PUT and POST do but GET does not. To support
auth-int
, you must set either the digest of the entity-body
(using
entityBodyDigest(byte[])
) or the entity-body
itself (using
entityBody(byte[])
).
java.security.SecureRandom
, which should be suitable for most purposes.
If you still for some reason need to override the default client nonce you can set it using
clientNonce(String)
. You may also have to call firstRequestClientNonce(String)
,
see the documentation of that method for details.
Modifier and Type | Field and Description |
---|---|
static java.lang.String |
HTTP_HEADER_AUTHORIZATION
The name of the HTTP request header ("Authorization").
|
Constructor and Description |
---|
DigestChallengeResponse()
Creates an empty challenge response.
|
Modifier and Type | Method and Description |
---|---|
DigestChallengeResponse |
algorithm(java.lang.String algorithm)
Sets the
algorithm directive, which must be the same as the algorithm directive
of the challenge. |
DigestChallengeResponse |
challenge(DigestChallenge challenge)
Sets the values of the
realm , nonce , opaque , and algorithm
directives and the supported quality of protection types based on a challenge. |
DigestChallengeResponse |
clientNonce(java.lang.String clientNonce)
Sets the
cnonce directive, which is a random string generated by the client that will
be included in the challenge response hash. |
DigestChallengeResponse |
digestUri(java.lang.String digestUri)
Sets the
digest-uri directive, which must be exactly the same as the
Request-URI of the Request-Line of the HTTP request. |
DigestChallengeResponse |
entityBody(byte[] entityBody)
Sets the
entity-body of the request, which is only used for the "auth-int" quality of
protection. |
DigestChallengeResponse |
entityBodyDigest(byte[] entityBodyDigest)
Sets the digest of the
entity-body of the request, which is only used for the
"auth-int" quality of protection. |
DigestChallengeResponse |
firstRequestClientNonce(java.lang.String firstRequestClientNonce)
Sets the value of client nonce used in the first request.
|
java.lang.String |
getAlgorithm()
Returns the value of the
algorithm directive. |
java.lang.String |
getClientNonce()
Returns the value of the
cnonce directive. |
java.lang.String |
getDigestUri()
Returns the value of the
digest-uri directive. |
byte[] |
getEntityBodyDigest()
Returns the digest of the
entity-body . |
java.lang.String |
getFirstRequestClientNonce()
Returns the value of client nonce used in the first request.
|
java.lang.String |
getHeaderValue()
Returns the
credentials , that is, the string to set in the Authorization
HTTP request header. |
java.lang.String |
getNonce()
Returns the unquoted value of the
nonce directive |
int |
getNonceCount()
Returns the integer representation of the
nonce-count directive. |
java.lang.String |
getOpaque()
Returns the the unquoted value of the
opaque directive, or null . |
java.lang.String |
getPassword()
Returns the password to use for authentication.
|
DigestChallenge.QualityOfProtection |
getQop()
Returns the "quality of protection" that will be used for the response.
|
java.lang.String |
getQuotedNonce()
Returns the quoted value of the
nonce directive. |
java.lang.String |
getQuotedOpaque()
Returns the the quoted value of the
opaque directive, or null . |
java.lang.String |
getQuotedRealm()
Returns the quoted value of the
realm directive. |
java.lang.String |
getRealm()
Returns the unquoted value of the
realm directive. |
java.lang.String |
getRequestMethod()
Returns the HTTP method of the request.
|
java.util.Set<DigestChallenge.QualityOfProtection> |
getSupportedQopTypes()
Returns the type of "quality of protection" that can be used when responding to the request.
|
java.lang.String |
getUsername()
Returns the username to use for authentication.
|
DigestChallengeResponse |
incrementNonceCount()
Increments the value of the
nonce-count by one. |
static boolean |
isAlgorithmSupported(java.lang.String algorithm)
Returns
true if a given digest algorithm is supported. |
static boolean |
isChallengeSupported(DigestChallenge challenge)
Returns
true if a given challenge is supported and a response to it can be generated
(given that all other required values are supplied). |
boolean |
isEntityBodyDigestRequired()
Returns
true if the digest of the entity-body is required to generate an
authorization header for this response. |
DigestChallengeResponse |
nonce(java.lang.String unquotedNonce)
Sets the
nonce directive, which must be the same as the nonce directive of the
challenge. |
DigestChallengeResponse |
nonceCount(int nonceCount)
Sets the integer representation of the
nonce-count directive, which indicates how many
times this a challenge response with this nonce has been used. |
DigestChallengeResponse |
opaque(java.lang.String unquotedOpaque)
Sets the
opaque directive, which must be the same as the opaque directive of
the challenge. |
DigestChallengeResponse |
password(java.lang.String password)
Sets the password to use for authentication.
|
DigestChallengeResponse |
quotedNonce(java.lang.String quotedNonce)
Sets the
nonce directive, which must be the same as the nonce directive of the
challenge. |
DigestChallengeResponse |
quotedOpaque(java.lang.String quotedOpaque)
Sets the
opaque directive, which must be the same as the opaque directive of
the challenge. |
DigestChallengeResponse |
quotedRealm(java.lang.String quotedRealm)
Sets the
realm directive, which must be the same as the realm directive of
the challenge. |
DigestChallengeResponse |
randomizeClientNonce()
Sets the
cnonce directive to a random value. |
DigestChallengeResponse |
realm(java.lang.String unquotedRealm)
Sets the
realm directive, which must be the same as the realm directive of
the challenge. |
DigestChallengeResponse |
requestMethod(java.lang.String requestMethod)
Sets the HTTP method of the request (GET, POST, etc).
|
DigestChallengeResponse |
resetNonceCount()
Sets the value of the
nonce-count to one. |
static DigestChallengeResponse |
responseTo(DigestChallenge challenge)
Creates a digest challenge response, setting the values of the
realm , nonce ,
opaque , and algorithm directives and the supported quality of protection
types based on a challenge. |
DigestChallengeResponse |
supportedQopTypes(java.util.Set<DigestChallenge.QualityOfProtection> supportedQopTypes)
Sets the type of "quality of protection" that can be used when responding to the request.
|
java.lang.String |
toString() |
DigestChallengeResponse |
username(java.lang.String username)
Sets the username to use for authentication.
|
public static final java.lang.String HTTP_HEADER_AUTHORIZATION
public DigestChallengeResponse()
Consider using responseTo(DigestChallenge)
when creating a response to a specific
challenge.
public static boolean isChallengeSupported(DigestChallenge challenge)
true
if a given challenge is supported and a response to it can be generated
(given that all other required values are supplied).
For a challenge to be supported, the following requirements must be met:
isAlgorithmSupported(String)
).supportedQopTypes(Set)
.challenge
- the challengetrue
if the challenge is supportedpublic static DigestChallengeResponse responseTo(DigestChallenge challenge)
realm
, nonce
,
opaque
, and algorithm
directives and the supported quality of protection
types based on a challenge.
If the challenge is not supported an exception is thrown. Use
isChallengeSupported(DigestChallenge)
to check if a challenge is supported before
calling this method.
challenge
- the challengejava.lang.IllegalArgumentException
- if the challenge is not supportedisChallengeSupported(DigestChallenge)
public static boolean isAlgorithmSupported(java.lang.String algorithm)
true
if a given digest algorithm is supported.
Supported values are "MD5", "MD5-sess", "SHA-256", "SHA-256-sess", and null
. null
indicates that the digest is generated using MD5, but no algorithm
directive is
included in the response.
algorithm
- the algorithmtrue
if the algorithm is supportedpublic DigestChallengeResponse algorithm(java.lang.String algorithm)
algorithm
directive, which must be the same as the algorithm
directive
of the challenge.
Use isAlgorithmSupported(String)
to check if a particular algorithm is supported.
Note: Setting the algorithm will also reset the entity-body
to a byte array of
zero length, see entityBody(byte[])
. This is because the entity-body
is
not stored, only the digest is. If you need to set both entity-body
and
algorithm
, make sure to set algorithm
first.
algorithm
- the value of the algorithm
directive or null
to not include an
algorithm in the responsejava.lang.IllegalArgumentException
- if the algorithm is not supportedgetAlgorithm()
,
isAlgorithmSupported(String)
,
Section 3.2.2 of RFC 2617public java.lang.String getAlgorithm()
algorithm
directive.algorithm
directive or null
if algorithm
is
not setalgorithm(String)
public DigestChallengeResponse username(java.lang.String username)
username
- the usernamegetUsername()
,
Section 3.2.2 of RFC 2617public java.lang.String getUsername()
username(String)
public DigestChallengeResponse password(java.lang.String password)
password
- the passwordgetPassword()
public java.lang.String getPassword()
password(String)
public DigestChallengeResponse clientNonce(java.lang.String clientNonce)
cnonce
directive, which is a random string generated by the client that will
be included in the challenge response hash.
There is normally no need to manually set the client nonce since it will have a default
value of a randomly generated string. If you do, make sure to call
firstRequestClientNonce(String)
if you modify the client nonce for the first request,
or some session variants of algorithms (those ending in -sess
) may not work.
clientNonce
- The unquoted value of the cnonce
directive.getClientNonce()
,
randomizeClientNonce()
,
Section 3.2.2 of RFC 2617public java.lang.String getClientNonce()
cnonce
directive.
Unless overridden by calling clientNonce(String)
, the cnonce
directive is
set to a randomly generated string.
cnonce
directiveclientNonce(String)
public DigestChallengeResponse randomizeClientNonce()
cnonce
directive to a random value.clientNonce(String)
,
getClientNonce()
public DigestChallengeResponse firstRequestClientNonce(java.lang.String firstRequestClientNonce)
This value is used in session based algorithms (those ending in -sess
). If the
challenge is reused for multiple request, the original client nonce used when responding to
the original challenge is used in subsequent challenge responses, even if the client changes
the client nonce for subsequent requests.
Normally, there is no need to call this method. The default value of the client nonce is a randomly generated string, and the default value of the first request client nonce is the same string. It is only if you override the default value and supply your own client nonce for the first request that you must make sure to call this method with the same value:
response.clientNonce("my own client nonce").firstRequestClientNonce(response.getClientNonce());
firstRequestClientNonce
- the client nonce value used in the first requestgetFirstRequestClientNonce()
,
clientNonce(String)
,
getClientNonce()
,
randomizeClientNonce()
,
Section 3.2.2.2, A1, of RFC
2617public java.lang.String getFirstRequestClientNonce()
This value is used in session based algorithms (those ending in -sess
). If the
challenge is reused for multiple request, the original client nonce used when responding to
the original challenge is used in subsequent challenge responses, even if the client changes
the client nonce for subsequent requests.
firstRequestClientNonce(String)
,
clientNonce(String)
,
getClientNonce()
,
randomizeClientNonce()
,
Section 3.2.2.2, A1, of RFC
2617public DigestChallengeResponse quotedNonce(java.lang.String quotedNonce)
nonce
directive, which must be the same as the nonce directive of the
challenge.
Setting the nonce
directive resets the nonce count to one.
quotedNonce
- the quoted value of the nonce
directivegetQuotedNonce()
,
nonce(String)
,
getNonce()
,
Section 3.2.2 of RFC 2617public java.lang.String getQuotedNonce()
nonce
directive.nonce
directivequotedNonce(String)
,
nonce(String)
,
getNonce()
public DigestChallengeResponse nonce(java.lang.String unquotedNonce)
nonce
directive, which must be the same as the nonce
directive of the
challenge.
Setting the nonce directive resets the nonce count to one.
unquotedNonce
- the unquoted value of the nonce
directivegetNonce()
,
quotedNonce(String)
,
getQuotedNonce()
,
Section 3.2.2 of RFC 2617public java.lang.String getNonce()
nonce
directivenonce
directivenonce(String)
,
quotedNonce(String)
,
getQuotedNonce()
public DigestChallengeResponse nonceCount(int nonceCount)
nonce-count
directive, which indicates how many
times this a challenge response with this nonce has been used.
This is useful when using a challenge response from a previous challenge when sending a request. For each time a challenge response is used, the nonce count should be increased by one.
nonceCount
- integer representation of the nonce-count
directivegetNonceCount()
,
resetNonceCount()
,
incrementNonceCount()
,
Section 3.2.2 of RFC 2617public DigestChallengeResponse incrementNonceCount()
nonce-count
by one.nonceCount(int)
,
getNonceCount()
,
resetNonceCount()
public DigestChallengeResponse resetNonceCount()
nonce-count
to one.nonceCount(int)
,
getNonceCount()
,
incrementNonceCount()
public int getNonceCount()
nonce-count
directive.nonce-count
directivenonceCount(int)
,
resetNonceCount()
,
incrementNonceCount()
public DigestChallengeResponse quotedOpaque(java.lang.String quotedOpaque)
opaque
directive, which must be the same as the opaque
directive of
the challenge.quotedOpaque
- the quoted value of the opaque
directive, or null
if no
opaque directive should be included in the challenge responsegetQuotedOpaque()
,
opaque(String)
,
getOpaque()
,
Section 3.2.2 of RFC 2617public java.lang.String getQuotedOpaque()
opaque
directive, or null
.opaque
directive, or null
if the opaque
is not setquotedOpaque(String)
,
opaque(String)
,
getOpaque()
public DigestChallengeResponse opaque(java.lang.String unquotedOpaque)
opaque
directive, which must be the same as the opaque
directive of
the challenge.
Note: Since the value of the opaque
directive is always received from a challenge
quoted it is normally better to use the quotedOpaque(String)
method to avoid
unnecessary quoting/unquoting.
unquotedOpaque
- the unquoted value of the opaque
directive, or null
if no
opaque
directive should be included in the challenge responsegetOpaque()
,
quotedOpaque(String)
,
getQuotedOpaque()
,
Section 3.2.2 of RFC 2617public java.lang.String getOpaque()
opaque
directive, or null
.opaque
directive, or null
if the opaque
is not setopaque(String)
,
quotedOpaque(String)
,
getQuotedOpaque()
public DigestChallengeResponse supportedQopTypes(java.util.Set<DigestChallenge.QualityOfProtection> supportedQopTypes)
Normally, this value is sent by the server in the challenge, but setting it manually can be
used to force a particular qop type. To see which quality of protection that will be used in
the response, see getQop()
.
supportedQopTypes
- the types of quality of protection that the server supports, must not
be emptyjava.lang.IllegalArgumentException
- if supportedQopTypes is emptygetSupportedQopTypes()
,
getQop()
public java.util.Set<DigestChallenge.QualityOfProtection> getSupportedQopTypes()
supportedQopTypes(Set)
,
getQop()
public DigestChallenge.QualityOfProtection getQop()
This is a derived value, computed from the set of supported "quality of protection" types:
DigestChallenge.QualityOfProtection.AUTH
is supported it is used.DigestChallenge.QualityOfProtection.AUTH_INT
is supported it is used.DigestChallenge.QualityOfProtection.UNSPECIFIED_RFC2069_COMPATIBLE
is supported it
is used.null
will be returned.supportedQopTypes(Set)
,
getSupportedQopTypes()
,
entityBodyDigest(byte[])
,
getEntityBodyDigest()
,
entityBody(byte[])
public DigestChallengeResponse digestUri(java.lang.String digestUri)
digest-uri
directive, which must be exactly the same as the
Request-URI
of the Request-Line
of the HTTP request.
The digest URI is explained in Section 3.2.2 of RFC 2617, and refers to the explanation of Request-URI found in Section 5.1.2 of RFC 2616.
Examples: If the Request-Line
is
GET http://www.w3.org/pub/WWW/TheProject.html HTTP/1.1the
Request-URI
(and digest-uri
) is "http://www.w3.org/pub/WWW/TheProject.html
". If Request-Line
is
GET /pub/WWW/TheProject.html HTTP/1.1the
Request-URI
is "/pub/WWW/TheProject.html
".
This can be problematic since depending on the HTTP stack being used the Request-Line
and Request-URI
values may not be accessible. If in doubt, a sensible guess is to set
the digest-uri
to the path part of the URL being requested, for instance using
getPath()
in the URL
class.
digestUri
- the value of the digest-uri directivegetDigestUri()
public java.lang.String getDigestUri()
digest-uri
directive.digest-uri
directivedigestUri(String)
public DigestChallengeResponse quotedRealm(java.lang.String quotedRealm)
realm
directive, which must be the same as the realm
directive of
the challenge.quotedRealm
- the quoted value of the realm
directivegetQuotedRealm()
,
realm(String)
,
getRealm()
,
Section 3.2.1 of RFC 2617,
Section 3.2.2 of RFC 2617public java.lang.String getQuotedRealm()
realm
directive.realm
directivequotedRealm(String)
,
realm(String)
,
getRealm()
public DigestChallengeResponse realm(java.lang.String unquotedRealm)
realm
directive, which must be the same as the realm
directive of
the challenge.unquotedRealm
- the unquoted value of the realm
directivegetRealm()
,
quotedRealm(String)
,
getQuotedRealm()
,
Section 3.2.1 of RFC 2617,
Section 3.2.2 of RFC 2617public java.lang.String getRealm()
realm
directive.realm
directiverealm(String)
,
quotedRealm(String)
,
getQuotedRealm()
public DigestChallengeResponse requestMethod(java.lang.String requestMethod)
requestMethod
- the request methodgetRequestMethod()
,
Section 5.1.1 of RFC 2616public java.lang.String getRequestMethod()
requestMethod(String)
public DigestChallengeResponse entityBody(byte[] entityBody)
entity-body
of the request, which is only used for the "auth-int" quality of
protection.
The default value is a byte array of zero length. Note that the entity-body
is
not stored, to save space only the digest of the entity-body
is stored. For this
reason, changing the digest algorithm (see algorithm(String)
) will reset the
entity-body
to a zero-length array.
With "auth-int" quality of protection, the whole entity-body
of the message is
hashed and included in the response, providing some protection against tampering.
The entity-body
is not the same as the message-body
as explained in
RFC 2616, Section 7.2:
[…]The entity-body is obtained from the message-body by decoding any Transfer-Encoding that might have been applied to ensure safe and proper transfer of the message.So if, for example,
Transfer-Encoding
is gzip
, the entity-body
is the
unzipped message and the message-body
is the gzipped message.
Not all requests include an entity-body
, as explained in
RFC 2616, Section 4.3:
[…]The presence of a message-body in a request is signaled by the inclusion of a Content-Length or Transfer-Encoding header field in the request's message-headers.[…]In particular, PUT and POST requests include an
entity-body
(although it may be of
zero length), GET requests do not. To be fully conformant with the standards, "auth-int"
cannot be used to authenticate requests without an entity-body
. Some servers
implementations allow requests without an entity-body
to be authenticated using an
entity-body
of zero length.entityBody
- the entity-body
entityBodyDigest(byte[])
public DigestChallengeResponse entityBodyDigest(byte[] entityBodyDigest)
entity-body
of the request, which is only used for the
"auth-int" quality of protection.
Note that the entity-body
is not the same as the message-body
. See
entityBody(byte[])
for details.
Here is an example of how to compute the SHA-256 digest of an entity body:
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(entityBody);
byte[] digest = md.digest();
entityBodyDigest
- the digest of the entity-body
entityBody(byte[])
public byte[] getEntityBodyDigest()
entity-body
.entity-body
public boolean isEntityBodyDigestRequired()
true
if the digest of the entity-body
is required to generate an
authorization header for this response.
For most challenges the digest of the entity-body
is not used. It is only required
if getQop()
returns DigestChallenge.QualityOfProtection.AUTH_INT
.
true
if the digest of the entity-body
must be setentityBody(byte[])
,
entityBodyDigest(byte[])
,
getSupportedQopTypes()
public DigestChallengeResponse challenge(DigestChallenge challenge)
realm
, nonce
, opaque
, and algorithm
directives and the supported quality of protection types based on a challenge.
If the challenge is not supported an exception is thrown. Use
isChallengeSupported(DigestChallenge)
to check if a challenge is supported before
calling this method.
challenge
- the challengejava.lang.IllegalArgumentException
- if the challenge is not supportedisChallengeSupported(DigestChallenge)
public java.lang.String getHeaderValue()
credentials
, that is, the string to set in the Authorization
HTTP request header.
Before calling this method a number of values and directives must be set. The following are the most important:
If the qop type is auth-int
the following must also be set unless the default
value (zero-length entity body) is applicable:
The following directives must also be set, but are normally parsed from the challenge or have default values, listed here mostly for completions sake:
quotedRealm(String)
, parsed from the challenge.quotedNonce(String)
, parsed from the challenge.supportedQopTypes(Set)
, parsed from the challenge.clientNonce(String)
}, except if using the
DigestChallenge.QualityOfProtection.UNSPECIFIED_RFC2069_COMPATIBLE
qop (not recommended). Set to a
random value by default.firstRequestClientNonce(String)
if the algorithm is MD-sess. Set to the client
nonce by default.Authorization
HTTP request headerInsufficientInformationException
- if any of the mandatory directives and values
listed above has not been setpublic java.lang.String toString()
toString
in class java.lang.Object