/* * 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.pubkey; import java.io.Closeable; import java.io.IOException; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; 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.RuntimeSshException; 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.buffer.Buffer; import org.apache.sshd.common.util.buffer.BufferUtils; import org.apache.sshd.common.util.buffer.ByteArrayBuffer; /** * Implements the "publickey" authentication mechanism * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a> */ public class UserAuthPublicKey extends AbstractUserAuth implements SignatureFactoriesManager { public static final String NAME = UserAuthPublicKeyFactory.NAME; private Iterator<PublicKeyIdentity> keys; private PublicKeyIdentity current; private List<NamedFactory<Signature>> factories; public UserAuthPublicKey() { this(null); } public UserAuthPublicKey(List<NamedFactory<Signature>> factories) { super(NAME); this.factories = factories; // OK if null/empty } @Override public List<NamedFactory<Signature>> getSignatureFactories() { return factories; } @Override public void setSignatureFactories(List<NamedFactory<Signature>> factories) { this.factories = factories; } @Override public void init(ClientSession session, String service) throws Exception { super.init(session, service); releaseKeys(); // just making sure in case multiple calls to the method try { keys = new UserAuthPublicKeyIterator(session, this); } catch (Error e) { log.warn("init({})[{}] failed ({}) to initialize session keys: {}", session, service, e.getClass().getSimpleName(), e.getMessage()); if (log.isDebugEnabled()) { log.debug("init(" + session + ")[" + service + "] session keys initialization failure details", e); } throw new RuntimeSshException(e); } } @Override protected boolean sendAuthDataRequest(ClientSession session, String service) throws Exception { try { if ((keys == null) || (!keys.hasNext())) { if (log.isDebugEnabled()) { log.debug("sendAuthDataRequest({})[{}] no more keys to send", session, service); } return false; } current = keys.next(); } catch (Error e) { log.warn("sendAuthDataRequest({})[{}] failed ({}) to get next key: {}", session, service, e.getClass().getSimpleName(), e.getMessage()); if (log.isDebugEnabled()) { log.debug("sendAuthDataRequest(" + session + ")[" + service + "] next key fetch failure details", e); } throw new RuntimeSshException(e); } if (log.isTraceEnabled()) { log.trace("sendAuthDataRequest({})[{}] current key details: {}", session, service, current); } PublicKey key; try { key = current.getPublicKey(); } catch (Error e) { log.warn("sendAuthDataRequest({})[{}] failed ({}) to retrieve public key: {}", session, service, e.getClass().getSimpleName(), e.getMessage()); if (log.isDebugEnabled()) { log.debug("sendAuthDataRequest(" + session + ")[" + service + "] public key retrieval failure details", e); } throw new RuntimeSshException(e); } String algo = KeyUtils.getKeyType(key); String name = getName(); if (log.isDebugEnabled()) { log.debug("sendAuthDataRequest({})[{}] send SSH_MSG_USERAUTH_REQUEST request {} type={} - fingerprint={}", session, service, name, algo, KeyUtils.getFingerPrint(key)); } Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST); buffer.putString(session.getUsername()); buffer.putString(service); buffer.putString(name); buffer.putBoolean(false); buffer.putString(algo); buffer.putPublicKey(key); session.writePacket(buffer); return true; } @Override protected boolean processAuthDataRequest(ClientSession session, String service, Buffer buffer) throws Exception { String name = getName(); int cmd = buffer.getUByte(); if (cmd != SshConstants.SSH_MSG_USERAUTH_PK_OK) { throw new IllegalStateException("processAuthDataRequest(" + session + ")[" + service + "][" + name + "]" + " received unknown packet: cmd=" + SshConstants.getCommandMessageName(cmd)); } /* * Make sure the server echo-ed the same key we sent as * sanctioned by RFC4252 section 7 */ PublicKey key; try { key = current.getPublicKey(); } catch (Error e) { log.warn("processAuthDataRequest({})[{}][{}] failed ({}) to retrieve public key: {}", session, service, name, e.getClass().getSimpleName(), e.getMessage()); if (log.isDebugEnabled()) { log.debug("processAuthDataRequest(" + session + ")[" + service + "][" + name + "] public key retrieval failure details", e); } throw new RuntimeSshException(e); } String algo = KeyUtils.getKeyType(key); String rspKeyType = buffer.getString(); if (!rspKeyType.equals(algo)) { throw new InvalidKeySpecException("processAuthDataRequest(" + session + ")[" + service + "][" + name + "]" + " mismatched key types: expected=" + algo + ", actual=" + rspKeyType); } PublicKey rspKey = buffer.getPublicKey(); if (!KeyUtils.compareKeys(rspKey, key)) { throw new InvalidKeySpecException("processAuthDataRequest(" + session + ")[" + service + "][" + name + "]" + " mismatched " + algo + " keys: expected=" + KeyUtils.getFingerPrint(key) + ", actual=" + KeyUtils.getFingerPrint(rspKey)); } if (log.isDebugEnabled()) { log.debug("processAuthDataRequest({})[{}][{}] SSH_MSG_USERAUTH_PK_OK type={}, fingerprint={}", session, service, name, rspKeyType, KeyUtils.getFingerPrint(rspKey)); } String username = session.getUsername(); buffer = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST, GenericUtils.length(username) + GenericUtils.length(service) + GenericUtils.length(name) + GenericUtils.length(algo) + ByteArrayBuffer.DEFAULT_SIZE + Long.SIZE); buffer.putString(username); buffer.putString(service); buffer.putString(name); buffer.putBoolean(true); buffer.putString(algo); buffer.putPublicKey(key); appendSignature(session, service, name, username, algo, key, buffer); session.writePacket(buffer); return true; } protected void appendSignature(ClientSession session, String service, String name, String username, String algo, PublicKey key, Buffer buffer) throws Exception { byte[] id = session.getSessionId(); Buffer bs = new ByteArrayBuffer(id.length + username.length() + service.length() + name.length() + algo.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.putBoolean(true); bs.putString(algo); bs.putPublicKey(key); byte[] contents = bs.getCompactData(); byte[] sig; try { sig = current.sign(contents); } catch (Error e) { log.warn("appendSignature({})[{}][{}] failed ({}) to sign contents: {}", session, service, name, e.getClass().getSimpleName(), e.getMessage()); if (log.isDebugEnabled()) { log.debug("appendSignature(" + session + ")[" + service + "][" + name + "] signing failure details", e); } throw new RuntimeSshException(e); } if (log.isTraceEnabled()) { log.trace("appendSignature({})[{}] name={}, key type={}, fingerprint={} - verification data={}", session, service, name, algo, KeyUtils.getFingerPrint(key), BufferUtils.toHex(contents)); log.trace("appendSignature({})[{}] name={}, key type={}, fingerprint={} - generated signature={}", session, service, name, algo, KeyUtils.getFingerPrint(key), BufferUtils.toHex(sig)); } bs.clear(); bs.putString(algo); bs.putBytes(sig); buffer.putBytes(bs.array(), bs.rpos(), bs.available()); } @Override public void destroy() { try { releaseKeys(); } catch (IOException e) { throw new RuntimeException("Failed (" + e.getClass().getSimpleName() + ") to close agent: " + e.getMessage(), e); } super.destroy(); // for logging } protected void releaseKeys() throws IOException { try { if (keys instanceof Closeable) { if (log.isTraceEnabled()) { log.trace("releaseKeys({}) closing {}", getClientSession(), keys); } ((Closeable) keys).close(); } } finally { keys = null; } } }