/* * 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. */ package org.apache.sshd.client.auth.hostbased; import java.security.KeyPair; import java.security.PublicKey; import java.security.cert.X509Certificate; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.apache.sshd.client.auth.AbstractUserAuth; import org.apache.sshd.client.session.ClientSession; import org.apache.sshd.common.NamedFactory; import org.apache.sshd.common.SshConstants; import org.apache.sshd.common.config.keys.KeyUtils; import org.apache.sshd.common.signature.Signature; import org.apache.sshd.common.signature.SignatureFactoriesManager; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.OsUtils; import org.apache.sshd.common.util.Pair; import org.apache.sshd.common.util.ValidateUtils; import org.apache.sshd.common.util.buffer.Buffer; import org.apache.sshd.common.util.buffer.BufferUtils; import org.apache.sshd.common.util.buffer.ByteArrayBuffer; import org.apache.sshd.common.util.net.SshdSocketAddress; /** * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a> */ public class UserAuthHostBased extends AbstractUserAuth implements SignatureFactoriesManager { public static final String NAME = UserAuthHostBasedFactory.NAME; private Iterator<Pair<KeyPair, List<X509Certificate>>> keys; private final HostKeyIdentityProvider clientHostKeys; private List<NamedFactory<Signature>> factories; private String clientUsername; private String clientHostname; public UserAuthHostBased(HostKeyIdentityProvider clientHostKeys) { super(NAME); this.clientHostKeys = clientHostKeys; // OK if null } @Override public void init(ClientSession session, String service) throws Exception { super.init(session, service); keys = HostKeyIdentityProvider.iteratorOf(clientHostKeys); // in case multiple calls to the method } @Override public List<NamedFactory<Signature>> getSignatureFactories() { return factories; } @Override public void setSignatureFactories(List<NamedFactory<Signature>> factories) { this.factories = factories; } public String getClientUsername() { return clientUsername; } public void setClientUsername(String clientUsername) { this.clientUsername = clientUsername; } public String getClientHostname() { return clientHostname; } public void setClientHostname(String clientHostname) { this.clientHostname = clientHostname; } @Override protected boolean sendAuthDataRequest(ClientSession session, String service) throws Exception { String name = getName(); if ((keys == null) || (!keys.hasNext())) { if (log.isDebugEnabled()) { log.debug("sendAuthDataRequest({})[{}][{}] no more keys to send", session, service, name); } return false; } Pair<KeyPair, List<X509Certificate>> keyInfo = keys.next(); KeyPair kp = keyInfo.getFirst(); PublicKey pub = kp.getPublic(); String keyType = KeyUtils.getKeyType(pub); if (log.isTraceEnabled()) { log.trace("sendAuthDataRequest({})[{}][{}] current key details: type={}, fingerprint={}", session, service, name, keyType, KeyUtils.getFingerPrint(pub)); } Collection<NamedFactory<Signature>> factories = ValidateUtils.checkNotNullAndNotEmpty( SignatureFactoriesManager.resolveSignatureFactories(this, session), "No signature factories for session=%s", session); Signature verifier = ValidateUtils.checkNotNull( NamedFactory.create(factories, keyType), "No signer could be located for key type=%s", keyType); byte[] id = session.getSessionId(); String username = session.getUsername(); String clientUsername = resolveClientUsername(); String clientHostname = resolveClientHostname(); if (log.isDebugEnabled()) { log.debug("sendAuthDataRequest({})[{}][{}] client={}@{}", session, service, name, clientUsername, clientHostname); } Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST, id.length + username.length() + service.length() + clientUsername.length() + clientHostname.length() + keyType.length() + ByteArrayBuffer.DEFAULT_SIZE + Long.SIZE); buffer.clear(); buffer.putRawPublicKey(pub); List<X509Certificate> certs = keyInfo.getSecond(); if (GenericUtils.size(certs) > 0) { for (X509Certificate c : certs) { // TODO make sure this yields DER encoding buffer.putRawBytes(c.getEncoded()); } } byte[] keyBytes = buffer.getCompactData(); verifier.initSigner(kp.getPrivate()); buffer = session.prepareBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST, BufferUtils.clear(buffer)); buffer.putString(username); buffer.putString(service); buffer.putString(name); buffer.putString(keyType); buffer.putBytes(keyBytes); buffer.putString(clientHostname); buffer.putString(clientUsername); appendSignature(session, service, name, username, keyType, pub, keyBytes, clientHostname, clientUsername, verifier, buffer); session.writePacket(buffer); return true; } protected void appendSignature(ClientSession session, String service, String name, String username, String keyType, PublicKey key, byte[] keyBytes, String clientHostname, String clientUsername, Signature verifier, Buffer buffer) throws Exception { byte[] id = session.getSessionId(); Buffer bs = new ByteArrayBuffer(id.length + username.length() + service.length() + name.length() + keyType.length() + keyBytes.length + clientHostname.length() + clientUsername.length() + ByteArrayBuffer.DEFAULT_SIZE + Long.SIZE, false); bs.putBytes(id); bs.putByte(SshConstants.SSH_MSG_USERAUTH_REQUEST); bs.putString(username); bs.putString(service); bs.putString(name); bs.putString(keyType); bs.putBytes(keyBytes); bs.putString(clientHostname); bs.putString(clientUsername); verifier.update(bs.array(), bs.rpos(), bs.available()); byte[] signature = verifier.sign(); if (log.isTraceEnabled()) { log.trace("appendSignature({})[{}][{}] type={}, fingerprint={}, client={}@{}: signature={}", session, service, name, keyType, KeyUtils.getFingerPrint(key), clientUsername, clientHostname, BufferUtils.toHex(signature)); } bs.clear(); bs.putString(keyType); bs.putBytes(signature); buffer.putBytes(bs.array(), bs.rpos(), bs.available()); } @Override protected boolean processAuthDataRequest(ClientSession session, String service, Buffer buffer) throws Exception { int cmd = buffer.getUByte(); throw new IllegalStateException("processAuthDataRequest(" + session + ")[" + service + "]" + " received unknown packet: cmd=" + SshConstants.getCommandMessageName(cmd)); } protected String resolveClientUsername() { String value = getClientUsername(); return GenericUtils.isEmpty(value) ? OsUtils.getCurrentUser() : value; } protected String resolveClientHostname() { String value = getClientHostname(); if (GenericUtils.isEmpty(value)) { value = SshdSocketAddress.toAddressString(SshdSocketAddress.getFirstExternalNetwork4Address()); } return GenericUtils.isEmpty(value) ? SshdSocketAddress.LOCALHOST_IP : value; } }