All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.http.impl.auth.CredSspScheme Maven / Gradle / Ivy

There is a newer version: 4.5.14
Show newest version
/*
 * ====================================================================
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * .
 *
 */

package org.apache.http.impl.auth;


import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLEngineResult.Status;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Consts;
import org.apache.http.Header;
import org.apache.http.HttpRequest;
import org.apache.http.auth.AUTH;
import org.apache.http.auth.AuthenticationException;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.InvalidCredentialsException;
import org.apache.http.auth.MalformedChallengeException;
import org.apache.http.auth.NTCredentials;
import org.apache.http.message.BufferedHeader;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.CharArrayBuffer;
import org.apache.http.util.CharsetUtils;

/**
 * 

* Client implementation of the CredSSP protocol specified in [MS-CSSP]. *

*

* Note: This is implementation is NOT GSS based. It should be. But there is no Java NTLM * implementation as GSS module. Maybe the NTLMEngine can be converted to GSS and then this * can be also switched to GSS. In fact it only works in CredSSP+NTLM case. *

*

* Based on [MS-CSSP]: Credential Security Support Provider (CredSSP) Protocol (Revision 13.0, 7/14/2016). * The implementation was inspired by Python CredSSP and NTLM implementation by Jordan Borean. *

*/ public class CredSspScheme extends AuthSchemeBase { private static final Charset UNICODE_LITTLE_UNMARKED = CharsetUtils.lookup( "UnicodeLittleUnmarked" ); public static final String SCHEME_NAME = "CredSSP"; private final Log log = LogFactory.getLog( CredSspScheme.class ); enum State { // Nothing sent, nothing received UNINITIATED, // We are handshaking. Several messages are exchanged in this state TLS_HANDSHAKE, // TLS handshake finished. Channel established TLS_HANDSHAKE_FINISHED, // NTLM NEGOTIATE message sent (strictly speaking this should be SPNEGO) NEGO_TOKEN_SENT, // NTLM CHALLENGE message received (strictly speaking this should be SPNEGO) NEGO_TOKEN_RECEIVED, // NTLM AUTHENTICATE message sent together with a server public key PUB_KEY_AUTH_SENT, // Server public key authentication message received PUB_KEY_AUTH_RECEIVED, // Credentials message sent. Protocol exchange finished. CREDENTIALS_SENT; } private State state; private SSLEngine sslEngine; private NTLMEngineImpl.Type1Message type1Message; private NTLMEngineImpl.Type2Message type2Message; private NTLMEngineImpl.Type3Message type3Message; private CredSspTsRequest lastReceivedTsRequest; private NTLMEngineImpl.Handle ntlmOutgoingHandle; private NTLMEngineImpl.Handle ntlmIncomingHandle; private byte[] peerPublicKey; public CredSspScheme() { state = State.UNINITIATED; } @Override public String getSchemeName() { return SCHEME_NAME; } @Override public String getParameter( final String name ) { return null; } @Override public String getRealm() { return null; } @Override public boolean isConnectionBased() { return true; } private SSLEngine getSSLEngine() { if ( sslEngine == null ) { sslEngine = createSSLEngine(); } return sslEngine; } private SSLEngine createSSLEngine() { final SSLContext sslContext; try { sslContext = SSLContexts.custom().build(); } catch ( final NoSuchAlgorithmException e ) { throw new RuntimeException( "Error creating SSL Context: " + e.getMessage(), e ); } catch ( final KeyManagementException e ) { throw new RuntimeException( "Error creating SSL Context: " + e.getMessage(), e ); } final X509TrustManager tm = new X509TrustManager() { @Override public void checkClientTrusted( final X509Certificate[] chain, final String authType ) throws CertificateException { // Nothing to do. } @Override public void checkServerTrusted( final X509Certificate[] chain, final String authType ) throws CertificateException { // Nothing to do, accept all. CredSSP server is using its own certificate without any // binding to the PKI trust chains. The public key is verified as part of the CredSSP // protocol exchange. } @Override public X509Certificate[] getAcceptedIssuers() { return null; } }; try { sslContext.init( null, new TrustManager[] { tm }, null ); } catch ( final KeyManagementException e ) { throw new RuntimeException( "SSL Context initialization error: " + e.getMessage(), e ); } final SSLEngine sslEngine = sslContext.createSSLEngine(); sslEngine.setUseClientMode( true ); return sslEngine; } @Override protected void parseChallenge( final CharArrayBuffer buffer, final int beginIndex, final int endIndex ) throws MalformedChallengeException { final String inputString = buffer.substringTrimmed( beginIndex, endIndex ); if ( inputString.isEmpty() ) { if ( state == State.UNINITIATED ) { // This is OK, just send out first message. That should start TLS handshake } else { final String msg = "Received unexpected empty input in state " + state; log.error( msg ); throw new MalformedChallengeException( msg ); } } if ( state == State.TLS_HANDSHAKE ) { unwrapHandshake( inputString ); if ( getSSLEngine().getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING ) { log.trace( "TLS handshake finished" ); state = State.TLS_HANDSHAKE_FINISHED; } } if ( state == State.NEGO_TOKEN_SENT ) { final ByteBuffer buf = unwrap( inputString ); state = State.NEGO_TOKEN_RECEIVED; lastReceivedTsRequest = CredSspTsRequest.createDecoded( buf ); } if ( state == State.PUB_KEY_AUTH_SENT ) { final ByteBuffer buf = unwrap( inputString ); state = State.PUB_KEY_AUTH_RECEIVED; lastReceivedTsRequest = CredSspTsRequest.createDecoded( buf ); } } @Override @Deprecated public Header authenticate( final Credentials credentials, final HttpRequest request ) throws AuthenticationException { return authenticate( credentials, request, null ); } @Override public Header authenticate( final Credentials credentials, final HttpRequest request, final HttpContext context ) throws AuthenticationException { NTCredentials ntcredentials = null; try { ntcredentials = ( NTCredentials ) credentials; } catch ( final ClassCastException e ) { throw new InvalidCredentialsException( "Credentials cannot be used for CredSSP authentication: " + credentials.getClass().getName() ); } String outputString = null; if ( state == State.UNINITIATED ) { beginTlsHandshake(); outputString = wrapHandshake(); state = State.TLS_HANDSHAKE; } else if ( state == State.TLS_HANDSHAKE ) { outputString = wrapHandshake(); } else if ( state == State.TLS_HANDSHAKE_FINISHED ) { final int ntlmFlags = getNtlmFlags(); final ByteBuffer buf = allocateOutBuffer(); type1Message = new NTLMEngineImpl.Type1Message( ntcredentials.getDomain(), ntcredentials.getWorkstation(), ntlmFlags); final byte[] ntlmNegoMessageEncoded = type1Message.getBytes(); final CredSspTsRequest req = CredSspTsRequest.createNegoToken( ntlmNegoMessageEncoded ); req.encode( buf ); buf.flip(); outputString = wrap( buf ); state = State.NEGO_TOKEN_SENT; } else if ( state == State.NEGO_TOKEN_RECEIVED ) { final ByteBuffer buf = allocateOutBuffer(); type2Message = new NTLMEngineImpl.Type2Message( lastReceivedTsRequest.getNegoToken()); final Certificate peerServerCertificate = getPeerServerCertificate(); type3Message = new NTLMEngineImpl.Type3Message( ntcredentials.getDomain(), ntcredentials.getWorkstation(), ntcredentials.getUserName(), ntcredentials.getPassword(), type2Message.getChallenge(), type2Message.getFlags(), type2Message.getTarget(), type2Message.getTargetInfo(), peerServerCertificate, type1Message.getBytes(), type2Message.getBytes()); final byte[] ntlmAuthenticateMessageEncoded = type3Message.getBytes(); final byte[] exportedSessionKey = type3Message.getExportedSessionKey(); ntlmOutgoingHandle = new NTLMEngineImpl.Handle(exportedSessionKey, NTLMEngineImpl.Mode.CLIENT, true); ntlmIncomingHandle = new NTLMEngineImpl.Handle(exportedSessionKey, NTLMEngineImpl.Mode.SERVER, true); final CredSspTsRequest req = CredSspTsRequest.createNegoToken( ntlmAuthenticateMessageEncoded ); peerPublicKey = getSubjectPublicKeyDer( peerServerCertificate.getPublicKey() ); final byte[] pubKeyAuth = createPubKeyAuth(); req.setPubKeyAuth( pubKeyAuth ); req.encode( buf ); buf.flip(); outputString = wrap( buf ); state = State.PUB_KEY_AUTH_SENT; } else if ( state == State.PUB_KEY_AUTH_RECEIVED ) { verifyPubKeyAuthResponse( lastReceivedTsRequest.getPubKeyAuth() ); final byte[] authInfo = createAuthInfo( ntcredentials ); final CredSspTsRequest req = CredSspTsRequest.createAuthInfo( authInfo ); final ByteBuffer buf = allocateOutBuffer(); req.encode( buf ); buf.flip(); outputString = wrap( buf ); state = State.CREDENTIALS_SENT; } else { throw new AuthenticationException( "Wrong state " + state ); } final CharArrayBuffer buffer = new CharArrayBuffer( 32 ); if ( isProxy() ) { buffer.append( AUTH.PROXY_AUTH_RESP ); } else { buffer.append( AUTH.WWW_AUTH_RESP ); } buffer.append( ": CredSSP " ); buffer.append( outputString ); return new BufferedHeader( buffer ); } private int getNtlmFlags() { return NTLMEngineImpl.FLAG_REQUEST_OEM_ENCODING | NTLMEngineImpl.FLAG_REQUEST_SIGN | NTLMEngineImpl.FLAG_REQUEST_SEAL | NTLMEngineImpl.FLAG_DOMAIN_PRESENT | NTLMEngineImpl.FLAG_REQUEST_ALWAYS_SIGN | NTLMEngineImpl.FLAG_REQUEST_NTLM2_SESSION | NTLMEngineImpl.FLAG_TARGETINFO_PRESENT | NTLMEngineImpl.FLAG_REQUEST_VERSION | NTLMEngineImpl.FLAG_REQUEST_128BIT_KEY_EXCH | NTLMEngineImpl.FLAG_REQUEST_EXPLICIT_KEY_EXCH | NTLMEngineImpl.FLAG_REQUEST_56BIT_ENCRYPTION; } private Certificate getPeerServerCertificate() throws AuthenticationException { final Certificate[] peerCertificates; try { peerCertificates = sslEngine.getSession().getPeerCertificates(); } catch ( final SSLPeerUnverifiedException e ) { throw new AuthenticationException( e.getMessage(), e ); } for ( final Certificate peerCertificate : peerCertificates ) { if ( !( peerCertificate instanceof X509Certificate ) ) { continue; } final X509Certificate peerX509Cerificate = ( X509Certificate ) peerCertificate; if ( peerX509Cerificate.getBasicConstraints() != -1 ) { continue; } return peerX509Cerificate; } return null; } private byte[] createPubKeyAuth() throws AuthenticationException { return ntlmOutgoingHandle.signAndEncryptMessage( peerPublicKey ); } private void verifyPubKeyAuthResponse( final byte[] pubKeyAuthResponse ) throws AuthenticationException { final byte[] pubKeyReceived = ntlmIncomingHandle.decryptAndVerifySignedMessage( pubKeyAuthResponse ); // assert: pubKeyReceived = peerPublicKey + 1 // The following algorithm is a bit simplified. But due to the ASN.1 encoding the first byte // of the public key will be 0x30 we can pretty much rely on a fact that there will be no carry if ( peerPublicKey.length != pubKeyReceived.length ) { throw new AuthenticationException( "Public key mismatch in pubKeyAuth response" ); } if ( ( peerPublicKey[0] + 1 ) != pubKeyReceived[0] ) { throw new AuthenticationException( "Public key mismatch in pubKeyAuth response" ); } for ( int i = 1; i < peerPublicKey.length; i++ ) { if ( peerPublicKey[i] != pubKeyReceived[i] ) { throw new AuthenticationException( "Public key mismatch in pubKeyAuth response" ); } } log.trace( "Received public key response is valid" ); } private byte[] createAuthInfo( final NTCredentials ntcredentials ) throws AuthenticationException { final byte[] domainBytes = encodeUnicode( ntcredentials.getDomain() ); final byte[] domainOctetStringBytesLengthBytes = encodeLength( domainBytes.length ); final int domainNameLength = 1 + domainOctetStringBytesLengthBytes.length + domainBytes.length; final byte[] domainNameLengthBytes = encodeLength( domainNameLength ); final byte[] usernameBytes = encodeUnicode( ntcredentials.getUserName() ); final byte[] usernameOctetStringBytesLengthBytes = encodeLength( usernameBytes.length ); final int userNameLength = 1 + usernameOctetStringBytesLengthBytes.length + usernameBytes.length; final byte[] userNameLengthBytes = encodeLength( userNameLength ); final byte[] passwordBytes = encodeUnicode( ntcredentials.getPassword() ); final byte[] passwordOctetStringBytesLengthBytes = encodeLength( passwordBytes.length ); final int passwordLength = 1 + passwordOctetStringBytesLengthBytes.length + passwordBytes.length; final byte[] passwordLengthBytes = encodeLength( passwordLength ); final int tsPasswordLength = 1 + domainNameLengthBytes.length + domainNameLength + 1 + userNameLengthBytes.length + userNameLength + 1 + passwordLengthBytes.length + passwordLength; final byte[] tsPasswordLengthBytes = encodeLength( tsPasswordLength ); final int credentialsOctetStringLength = 1 + tsPasswordLengthBytes.length + tsPasswordLength; final byte[] credentialsOctetStringLengthBytes = encodeLength( credentialsOctetStringLength ); final int credentialsLength = 1 + credentialsOctetStringLengthBytes.length + credentialsOctetStringLength; final byte[] credentialsLengthBytes = encodeLength( credentialsLength ); final int tsCredentialsLength = 5 + 1 + credentialsLengthBytes.length + credentialsLength; final byte[] tsCredentialsLengthBytes = encodeLength( tsCredentialsLength ); final ByteBuffer buf = ByteBuffer.allocate( 1 + tsCredentialsLengthBytes.length + tsCredentialsLength ); // TSCredentials structure [MS-CSSP] section 2.2.1.2 buf.put( ( byte ) 0x30 ); // seq buf.put( tsCredentialsLengthBytes ); buf.put( ( byte ) ( 0x00 | 0xa0 ) ); // credType tag [0] buf.put( ( byte ) 3 ); // credType length buf.put( ( byte ) 0x02 ); // type: INTEGER buf.put( ( byte ) 1 ); // credType inner length buf.put( ( byte ) 1 ); // credType value: 1 (password) buf.put( ( byte ) ( 0x01 | 0xa0 ) ); // credentials tag [1] buf.put( credentialsLengthBytes ); buf.put( ( byte ) 0x04 ); // type: OCTET STRING buf.put( credentialsOctetStringLengthBytes ); // TSPasswordCreds structure [MS-CSSP] section 2.2.1.2.1 buf.put( ( byte ) 0x30 ); // seq buf.put( tsPasswordLengthBytes ); buf.put( ( byte ) ( 0x00 | 0xa0 ) ); // domainName tag [0] buf.put( domainNameLengthBytes ); buf.put( ( byte ) 0x04 ); // type: OCTET STRING buf.put( domainOctetStringBytesLengthBytes ); buf.put( domainBytes ); buf.put( ( byte ) ( 0x01 | 0xa0 ) ); // userName tag [1] buf.put( userNameLengthBytes ); buf.put( ( byte ) 0x04 ); // type: OCTET STRING buf.put( usernameOctetStringBytesLengthBytes ); buf.put( usernameBytes ); buf.put( ( byte ) ( 0x02 | 0xa0 ) ); // password tag [2] buf.put( passwordLengthBytes ); buf.put( ( byte ) 0x04 ); // type: OCTET STRING buf.put( passwordOctetStringBytesLengthBytes ); buf.put( passwordBytes ); final byte[] authInfo = buf.array(); try { return ntlmOutgoingHandle.signAndEncryptMessage( authInfo ); } catch ( final NTLMEngineException e ) { throw new AuthenticationException( e.getMessage(), e ); } } private final static byte[] EMPTYBUFFER = new byte[0]; private byte[] encodeUnicode( final String string ) { if (string == null) { return EMPTYBUFFER; } return string.getBytes( UNICODE_LITTLE_UNMARKED ); } private byte[] getSubjectPublicKeyDer( final PublicKey publicKey ) throws AuthenticationException { // The publicKey.getEncoded() returns encoded SubjectPublicKeyInfo structure. But the CredSSP expects // SubjectPublicKey subfield. I have found no easy way how to get just the SubjectPublicKey from // java.security libraries. So let's use a primitive way and parse it out from the DER. try { final byte[] encodedPubKeyInfo = publicKey.getEncoded(); final ByteBuffer buf = ByteBuffer.wrap( encodedPubKeyInfo ); getByteAndAssert( buf, 0x30, "initial sequence" ); parseLength( buf ); getByteAndAssert( buf, 0x30, "AlgorithmIdentifier sequence" ); final int algIdSeqLength = parseLength( buf ); buf.position( buf.position() + algIdSeqLength ); getByteAndAssert( buf, 0x03, "subjectPublicKey type" ); int subjectPublicKeyLegth = parseLength( buf ); // There may be leading padding byte ... or whatever that is. Skip that. final byte b = buf.get(); if ( b == 0 ) { subjectPublicKeyLegth--; } else { buf.position( buf.position() - 1 ); } final byte[] subjectPublicKey = new byte[subjectPublicKeyLegth]; buf.get( subjectPublicKey ); return subjectPublicKey; } catch ( final MalformedChallengeException e ) { throw new AuthenticationException( e.getMessage(), e ); } } private void beginTlsHandshake() throws AuthenticationException { try { getSSLEngine().beginHandshake(); } catch ( final SSLException e ) { throw new AuthenticationException( "SSL Engine error: " + e.getMessage(), e ); } } private ByteBuffer allocateOutBuffer() { final SSLEngine sslEngine = getSSLEngine(); final SSLSession sslSession = sslEngine.getSession(); return ByteBuffer.allocate( sslSession.getApplicationBufferSize() ); } private String wrapHandshake() throws AuthenticationException { final ByteBuffer src = allocateOutBuffer(); src.flip(); final SSLEngine sslEngine = getSSLEngine(); final SSLSession sslSession = sslEngine.getSession(); // Needs to be twice the size as there may be two wraps during handshake. // Primitive and inefficient solution, but it works. final ByteBuffer dst = ByteBuffer.allocate( sslSession.getPacketBufferSize() * 2 ); while ( sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_WRAP ) { wrap( src, dst ); } dst.flip(); return encodeBase64( dst ); } private String wrap( final ByteBuffer src ) throws AuthenticationException { final SSLEngine sslEngine = getSSLEngine(); final SSLSession sslSession = sslEngine.getSession(); final ByteBuffer dst = ByteBuffer.allocate( sslSession.getPacketBufferSize() ); wrap( src, dst ); dst.flip(); return encodeBase64( dst ); } private void wrap( final ByteBuffer src, final ByteBuffer dst ) throws AuthenticationException { final SSLEngine sslEngine = getSSLEngine(); try { final SSLEngineResult engineResult = sslEngine.wrap( src, dst ); if ( engineResult.getStatus() != Status.OK ) { throw new AuthenticationException( "SSL Engine error status: " + engineResult.getStatus() ); } } catch ( final SSLException e ) { throw new AuthenticationException( "SSL Engine wrap error: " + e.getMessage(), e ); } } private void unwrapHandshake( final String inputString ) throws MalformedChallengeException { final SSLEngine sslEngine = getSSLEngine(); final SSLSession sslSession = sslEngine.getSession(); final ByteBuffer src = decodeBase64( inputString ); final ByteBuffer dst = ByteBuffer.allocate( sslSession.getApplicationBufferSize() ); while ( sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP ) { unwrap( src, dst ); } } private ByteBuffer unwrap( final String inputString ) throws MalformedChallengeException { final SSLEngine sslEngine = getSSLEngine(); final SSLSession sslSession = sslEngine.getSession(); final ByteBuffer src = decodeBase64( inputString ); final ByteBuffer dst = ByteBuffer.allocate( sslSession.getApplicationBufferSize() ); unwrap( src, dst ); dst.flip(); return dst; } private void unwrap( final ByteBuffer src, final ByteBuffer dst ) throws MalformedChallengeException { try { final SSLEngineResult engineResult = sslEngine.unwrap( src, dst ); if ( engineResult.getStatus() != Status.OK ) { throw new MalformedChallengeException( "SSL Engine error status: " + engineResult.getStatus() ); } if ( sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_TASK ) { final Runnable task = sslEngine.getDelegatedTask(); task.run(); } } catch ( final SSLException e ) { throw new MalformedChallengeException( "SSL Engine unwrap error: " + e.getMessage(), e ); } } private String encodeBase64( final ByteBuffer buffer ) { final int limit = buffer.limit(); final byte[] bytes = new byte[limit]; buffer.get( bytes ); return new String(Base64.encodeBase64(bytes), Consts.ASCII); } private ByteBuffer decodeBase64( final String inputString ) { final byte[] inputBytes = Base64.decodeBase64(inputString.getBytes(Consts.ASCII)); final ByteBuffer buffer = ByteBuffer.wrap( inputBytes ); return buffer; } @Override public boolean isComplete() { return state == State.CREDENTIALS_SENT; } /** * Implementation of the TsRequest structure used in CredSSP protocol. * It is specified in [MS-CPPS] section 2.2.1. */ static class CredSspTsRequest { private static final int VERSION = 3; private byte[] negoToken; private byte[] authInfo; private byte[] pubKeyAuth; protected CredSspTsRequest() { super(); } public static CredSspTsRequest createNegoToken( final byte[] negoToken ) { final CredSspTsRequest req = new CredSspTsRequest(); req.negoToken = negoToken; return req; } public static CredSspTsRequest createAuthInfo( final byte[] authInfo ) { final CredSspTsRequest req = new CredSspTsRequest(); req.authInfo = authInfo; return req; } public static CredSspTsRequest createDecoded( final ByteBuffer buf ) throws MalformedChallengeException { final CredSspTsRequest req = new CredSspTsRequest(); req.decode( buf ); return req; } public byte[] getNegoToken() { return negoToken; } public void setNegoToken( final byte[] negoToken ) { this.negoToken = negoToken; } public byte[] getAuthInfo() { return authInfo; } public void setAuthInfo( final byte[] authInfo ) { this.authInfo = authInfo; } public byte[] getPubKeyAuth() { return pubKeyAuth; } public void setPubKeyAuth( final byte[] pubKeyAuth ) { this.pubKeyAuth = pubKeyAuth; } public void decode( final ByteBuffer buf ) throws MalformedChallengeException { negoToken = null; authInfo = null; pubKeyAuth = null; getByteAndAssert( buf, 0x30, "initial sequence" ); parseLength( buf ); while ( buf.hasRemaining() ) { final int contentTag = getAndAssertContentSpecificTag( buf, "content tag" ); parseLength( buf ); switch ( contentTag ) { case 0: processVersion( buf ); break; case 1: parseNegoTokens( buf ); break; case 2: parseAuthInfo( buf ); break; case 3: parsePubKeyAuth( buf ); break; case 4: processErrorCode( buf ); break; default: parseError( buf, "unexpected content tag " + contentTag ); } } } private void processVersion( final ByteBuffer buf ) throws MalformedChallengeException { getByteAndAssert( buf, 0x02, "version type" ); getLengthAndAssert( buf, 1, "version length" ); getByteAndAssert( buf, VERSION, "wrong protocol version" ); } private void parseNegoTokens( final ByteBuffer buf ) throws MalformedChallengeException { getByteAndAssert( buf, 0x30, "negoTokens sequence" ); parseLength( buf ); // I have seen both 0x30LL encoding and 0x30LL0x30LL encoding. Accept both. byte bufByte = buf.get(); if ( bufByte == 0x30 ) { parseLength( buf ); bufByte = buf.get(); } if ( ( bufByte & 0xff ) != 0xa0 ) { parseError( buf, "negoTokens: wrong content-specific tag " + String.format( "%02X", bufByte ) ); } parseLength( buf ); getByteAndAssert( buf, 0x04, "negoToken type" ); final int tokenLength = parseLength( buf ); negoToken = new byte[tokenLength]; buf.get( negoToken ); } private void parseAuthInfo( final ByteBuffer buf ) throws MalformedChallengeException { getByteAndAssert( buf, 0x04, "authInfo type" ); final int length = parseLength( buf ); authInfo = new byte[length]; buf.get( authInfo ); } private void parsePubKeyAuth( final ByteBuffer buf ) throws MalformedChallengeException { getByteAndAssert( buf, 0x04, "pubKeyAuth type" ); final int length = parseLength( buf ); pubKeyAuth = new byte[length]; buf.get( pubKeyAuth ); } private void processErrorCode( final ByteBuffer buf ) throws MalformedChallengeException { getLengthAndAssert( buf, 3, "error code length" ); getByteAndAssert( buf, 0x02, "error code type" ); getLengthAndAssert( buf, 1, "error code length" ); final byte errorCode = buf.get(); parseError( buf, "Error code " + errorCode ); } public void encode( final ByteBuffer buf ) { final ByteBuffer inner = ByteBuffer.allocate( buf.capacity() ); // version tag [0] inner.put( ( byte ) ( 0x00 | 0xa0 ) ); inner.put( ( byte ) 3 ); // length inner.put( ( byte ) ( 0x02 ) ); // INTEGER tag inner.put( ( byte ) 1 ); // length inner.put( ( byte ) VERSION ); // value if ( negoToken != null ) { int len = negoToken.length; final byte[] negoTokenLengthBytes = encodeLength( len ); len += 1 + negoTokenLengthBytes.length; final byte[] negoTokenLength1Bytes = encodeLength( len ); len += 1 + negoTokenLength1Bytes.length; final byte[] negoTokenLength2Bytes = encodeLength( len ); len += 1 + negoTokenLength2Bytes.length; final byte[] negoTokenLength3Bytes = encodeLength( len ); len += 1 + negoTokenLength3Bytes.length; final byte[] negoTokenLength4Bytes = encodeLength( len ); inner.put( ( byte ) ( 0x01 | 0xa0 ) ); // negoData tag [1] inner.put( negoTokenLength4Bytes ); // length inner.put( ( byte ) ( 0x30 ) ); // SEQUENCE tag inner.put( negoTokenLength3Bytes ); // length inner.put( ( byte ) ( 0x30 ) ); // .. of SEQUENCE tag inner.put( negoTokenLength2Bytes ); // length inner.put( ( byte ) ( 0x00 | 0xa0 ) ); // negoToken tag [0] inner.put( negoTokenLength1Bytes ); // length inner.put( ( byte ) ( 0x04 ) ); // OCTET STRING tag inner.put( negoTokenLengthBytes ); // length inner.put( negoToken ); } if ( authInfo != null ) { final byte[] authInfoEncodedLength = encodeLength( authInfo.length ); inner.put( ( byte ) ( 0x02 | 0xa0 ) ); // authInfo tag [2] inner.put( encodeLength( 1 + authInfoEncodedLength.length + authInfo.length ) ); // length inner.put( ( byte ) ( 0x04 ) ); // OCTET STRING tag inner.put( authInfoEncodedLength ); inner.put( authInfo ); } if ( pubKeyAuth != null ) { final byte[] pubKeyAuthEncodedLength = encodeLength( pubKeyAuth.length ); inner.put( ( byte ) ( 0x03 | 0xa0 ) ); // pubKeyAuth tag [3] inner.put( encodeLength( 1 + pubKeyAuthEncodedLength.length + pubKeyAuth.length ) ); // length inner.put( ( byte ) ( 0x04 ) ); // OCTET STRING tag inner.put( pubKeyAuthEncodedLength ); inner.put( pubKeyAuth ); } inner.flip(); // SEQUENCE tag buf.put( ( byte ) ( 0x10 | 0x20 ) ); buf.put( encodeLength( inner.limit() ) ); buf.put( inner ); } public String debugDump() { final StringBuilder sb = new StringBuilder( "TsRequest\n" ); sb.append( " negoToken:\n" ); sb.append( " " ); DebugUtil.dump( sb, negoToken ); sb.append( "\n" ); sb.append( " authInfo:\n" ); sb.append( " " ); DebugUtil.dump( sb, authInfo ); sb.append( "\n" ); sb.append( " pubKeyAuth:\n" ); sb.append( " " ); DebugUtil.dump( sb, pubKeyAuth ); return sb.toString(); } @Override public String toString() { return "TsRequest(negoToken=" + Arrays.toString( negoToken ) + ", authInfo=" + Arrays.toString( authInfo ) + ", pubKeyAuth=" + Arrays.toString( pubKeyAuth ) + ")"; } } static void getByteAndAssert( final ByteBuffer buf, final int expectedValue, final String errorMessage ) throws MalformedChallengeException { final byte bufByte = buf.get(); if ( bufByte != expectedValue ) { parseError( buf, errorMessage + expectMessage( expectedValue, bufByte ) ); } } private static String expectMessage( final int expectedValue, final int realValue ) { return "(expected " + String.format( "%02X", expectedValue ) + ", got " + String.format( "%02X", realValue ) + ")"; } static int parseLength( final ByteBuffer buf ) { byte bufByte = buf.get(); if ( bufByte == 0x80 ) { return -1; // infinite } if ( ( bufByte & 0x80 ) == 0x80 ) { final int size = bufByte & 0x7f; int length = 0; for ( int i = 0; i < size; i++ ) { bufByte = buf.get(); length = ( length << 8 ) + ( bufByte & 0xff ); } return length; } else { return bufByte; } } static void getLengthAndAssert( final ByteBuffer buf, final int expectedValue, final String errorMessage ) throws MalformedChallengeException { final int bufLength = parseLength( buf ); if ( expectedValue != bufLength ) { parseError( buf, errorMessage + expectMessage( expectedValue, bufLength ) ); } } static int getAndAssertContentSpecificTag( final ByteBuffer buf, final String errorMessage ) throws MalformedChallengeException { final byte bufByte = buf.get(); if ( ( bufByte & 0xe0 ) != 0xa0 ) { parseError( buf, errorMessage + ": wrong content-specific tag " + String.format( "%02X", bufByte ) ); } final int tag = bufByte & 0x1f; return tag; } static void parseError( final ByteBuffer buf, final String errorMessage ) throws MalformedChallengeException { throw new MalformedChallengeException( "Error parsing TsRequest (position:" + buf.position() + "): " + errorMessage ); } static byte[] encodeLength( final int length ) { if ( length < 128 ) { final byte[] encoded = new byte[1]; encoded[0] = ( byte ) length; return encoded; } int size = 1; int val = length; while ( ( val >>>= 8 ) != 0 ) { size++; } final byte[] encoded = new byte[1 + size]; encoded[0] = ( byte ) ( size | 0x80 ); int shift = ( size - 1 ) * 8; for ( int i = 0; i < size; i++ ) { encoded[i + 1] = ( byte ) ( length >> shift ); shift -= 8; } return encoded; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy