1 /* 2 * hunt-amqp: AMQP library for D programming language, based on hunt-net. 3 * 4 * Copyright (C) 2018-2019 HuntLabs 5 * 6 * Website: https://www.huntlabs.net 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 module hunt.amqp.impl.ProtonSaslClientAuthenticatorImpl; 12 13 import hunt.collection.Set; 14 15 //import javax.security.sasl.AuthenticationException; 16 //import javax.security.sasl.SaslException; 17 18 import hunt.amqp.sasl.MechanismMismatchException; 19 import hunt.amqp.sasl.ProtonSaslAuthenticator; 20 import hunt.proton.engine.Sasl; 21 import hunt.proton.engine.Transport; 22 23 import hunt.amqp.Handler; 24 import hunt.amqp.ProtonConnection; 25 import hunt.amqp.sasl.ProtonSaslMechanism; 26 import hunt.amqp.sasl.SaslSystemException; 27 import hunt.amqp.sasl.impl.ProtonSaslMechanismFinderImpl; 28 import hunt.net.Connection; 29 import hunt.Exceptions; 30 import hunt.logging; 31 32 alias NetSocket = hunt.net.Connection; 33 /** 34 * Manage the client side of the SASL authentication process. 35 */ 36 class ProtonSaslClientAuthenticatorImpl : ProtonSaslAuthenticator { 37 38 private Sasl sasl; 39 private string username; 40 private string password; 41 private ProtonSaslMechanism mechanism; 42 private Set!string mechanismsRestriction; 43 private Handler!ProtonConnection handler; 44 private NetSocket.Connection socket; 45 private ProtonConnection connection; 46 private bool _succeeded; 47 48 /** 49 * Create the authenticator and initialize it. 50 * 51 * @param username 52 * The username provide credentials to the remote peer, or null if there is none. 53 * @param password 54 * The password provide credentials to the remote peer, or null if there is none. 55 * @param allowedSaslMechanisms 56 * The possible mechanism(s) to which the client should restrict its mechanism selection to if offered by the 57 * server, or null/empty if no restriction. 58 * @param handler 59 * The handler to convey the result of the SASL process to. 60 * The async result will succeed if the SASL handshake completed successfully, it will fail with 61 * <ul> 62 * <li>a {@link MechanismMismatchException} if this client does not support any of the SASL 63 * mechanisms offered by the server,</li> 64 * <li>a {@link SaslSystemException} if the SASL handshake fails with either of the 65 * {@link SaslOutcome#PN_SASL_SYS}, {@link SaslOutcome#PN_SASL_TEMP} or 66 * {@link SaslOutcome#PN_SASL_PERM} outcomes,</li> 67 * <li>a {@code javax.security.sasl.AuthenticationException} if the handshake fails with 68 * the {@link SaslOutcome#PN_SASL_AUTH} outcome or</li> 69 * <li>a generic {@code javax.security.sasl.SaslException} if the handshake fails due to 70 * any other reason.</li> 71 * </ul> 72 */ 73 this(string username, string password, Set!string allowedSaslMechanisms ,Handler!ProtonConnection handler) { 74 this.handler = handler; 75 this.username = username; 76 this.password = password; 77 this.mechanismsRestriction = allowedSaslMechanisms; 78 } 79 80 public void init(NetSocket.Connection socket, ProtonConnection protonConnection, Transport transport) { 81 this.socket = socket; 82 this.connection = protonConnection; 83 this.sasl = transport.sasl(); 84 sasl.client(); 85 } 86 87 public void process(Handler!bool completionHandler) { 88 if (sasl is null) { 89 throw new IllegalStateException("Init was not called with the associated transport"); 90 } 91 92 bool done = false; 93 _succeeded = false; 94 95 try { 96 switch (sasl.getState()) { 97 case SaslState.PN_SASL_IDLE: 98 handleSaslInit(); 99 break; 100 case SaslState.PN_SASL_STEP: 101 handleSaslStep(); 102 break; 103 case SaslState.PN_SASL_FAIL: 104 handleSaslFail(); 105 break; 106 case SaslState.PN_SASL_PASS: 107 done = true; 108 _succeeded = true; 109 version(HUNT_AMQP_DEBUG) logInfo("PN_SASL_PASS !!!"); 110 handler.handle(connection); 111 break; 112 default: 113 break; 114 } 115 } catch (Exception e) { 116 done = true; 117 try { 118 if (socket !is null) { 119 socket.close(); 120 } 121 } finally { 122 // handler.handle(Future.failedFuture(e)); 123 } 124 } 125 126 completionHandler.handle(done); 127 } 128 129 public bool succeeded() { 130 return _succeeded; 131 } 132 133 private void handleSaslInit() { 134 string[] remoteMechanisms = sasl.getRemoteMechanisms(); 135 if (remoteMechanisms !is null && remoteMechanisms.length != 0) { 136 mechanism = ProtonSaslMechanismFinderImpl.findMatchingMechanism( username, password, mechanismsRestriction, 137 remoteMechanisms); 138 if (mechanism !is null) { 139 mechanism.setUsername( username); 140 mechanism.setPassword( password); 141 142 sasl.setMechanisms( [ mechanism.getName()]); 143 byte[] response = mechanism.getInitialResponse(); 144 // if (response !is null) { 145 sasl.send( response, 0, cast(int)(response.length)); 146 //} 147 } else { 148 // throw new MechanismMismatchException( 149 // "Could not find a suitable SASL mechanism for the remote peer using the available credentials.", 150 // remoteMechanisms); 151 //} 152 logError( "Could not find a suitable SASL mechanism for the remote peer using the available credentials."); 153 } 154 } 155 } 156 157 private void handleSaslStep() { 158 if (sasl.pending() != 0) { 159 byte[] challenge = new byte[sasl.pending()]; 160 sasl.recv(challenge, 0, cast(int)challenge.length); 161 byte[] response = mechanism.getChallengeResponse(challenge); 162 sasl.send(response, 0, cast(int)response.length); 163 } 164 } 165 166 private void handleSaslFail() { 167 168 SaslOutcome tmp = sasl.getOutcome(); 169 if (tmp == SaslOutcome.PN_SASL_AUTH) 170 { 171 logError("Failed to authenticate"); 172 } 173 else if (tmp == SaslOutcome.PN_SASL_SYS || tmp == SaslOutcome.PN_SASL_TEMP) 174 { 175 logError("SASL handshake failed due to a transient error"); 176 } 177 else if (tmp == SaslOutcome.PN_SASL_PERM) 178 { 179 logError("SASL handshake failed due to an unrecoverable error"); 180 }else 181 { 182 logError("SASL handshake failed"); 183 } 184 185 //switch(sasl.getOutcome()) { 186 //case PN_SASL_AUTH: 187 // logError("Failed to authenticate"); 188 // break; 189 // // throw new SaslSystemException("Failed to authenticate"); 190 //case PN_SASL_SYS: 191 // goto case; 192 //case PN_SASL_TEMP: 193 // logError("SASL handshake failed due to a transient error"); 194 // break; 195 // // throw new SaslSystemException(false, "SASL handshake failed due to a transient error"); 196 //case PN_SASL_PERM: 197 // logError("SASL handshake failed due to an unrecoverable error"); 198 // break; 199 // // throw new SaslSystemException(true, "SASL handshake failed due to an unrecoverable error"); 200 //default: 201 // logError("SASL handshake failed"); 202 // break; 203 // // throw new SaslException("SASL handshake failed"); 204 //} 205 } 206 }