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

com.spotify.crtauth.agentsigner.AgentSigner Maven / Gradle / Ivy

Go to download

An implementation of the Signer interface in crtauth-java that connects to the local ssh-agent.

The newest version!
/**
 * Copyright (c) 2015 Spotify AB.
 *
 * Licensed 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 com.spotify.crtauth.agentsigner;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.base.Throwables;

import com.spotify.crtauth.Fingerprint;
import com.spotify.crtauth.exceptions.CrtAuthException;
import com.spotify.crtauth.exceptions.KeyNotFoundException;
import com.spotify.crtauth.signer.Signer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.nio.channels.Channels;
import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Iterator;

import jnr.unixsocket.UnixSocketAddress;
import jnr.unixsocket.UnixSocketChannel;

import static com.google.common.base.Strings.isNullOrEmpty;

/**
 * AgentSigner is intended for command line tools where their invoker
 * also controls an ssh-agent process that can be contacted via a UNIX
 * referenced by the SSH_AUTH_SOCK environment variable.
 */
public class AgentSigner implements Signer, AutoCloseable {

  private static final Logger log = LoggerFactory.getLogger(AgentSigner.class);

  private final AgentOutputStream out;
  private final AgentInputStream in;

  @SuppressWarnings("unused")
  public AgentSigner() throws CrtAuthException {
    try {
      final String socketPath = System.getenv("SSH_AUTH_SOCK");
      if (isNullOrEmpty(socketPath)) {
        throw new CrtAuthException(
            "The environment variable SSH_AUTH_SOCK is not set. Please configure your ssh-agent.");
      }

      final UnixSocketChannel channel = UnixSocketChannel.open(
          new UnixSocketAddress(new File(socketPath)));

      log.debug("connected to " + channel.getRemoteSocketAddress());

      out = new AgentOutputStream(Channels.newOutputStream(channel));
      in = new AgentInputStream(Channels.newInputStream(channel));
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  @VisibleForTesting
  AgentSigner(final AgentOutputStream out, final AgentInputStream in) {
    this.out = out;
    this.in = in;
  }

  @Override
  public byte[] sign(final byte[] data, final Fingerprint fingerprint)
      throws IllegalArgumentException, KeyNotFoundException {
    final PublicKey publicKey;
    try {
      publicKey = findKey(fingerprint);
    } catch (IOException e) {
      throw Throwables.propagate(e);
    }

    if (publicKey == null) {
      throw new KeyNotFoundException("Your ssh-agent does not have the required key added. "
                                     + "This usually indicates that ssh-add has not been run.");
    }

    final String keyType = "ssh-" + publicKey.getAlgorithm().toLowerCase();
    if (!keyType.equals(RSA.RSA_LABEL)) {
      throw Throwables.propagate(new CrtAuthException(String.format(
          "Unknown key type %s. This code currently only supports %s.", keyType, RSA.RSA_LABEL)));
    }

    try {
      out.signRequest((RSAPublicKey) publicKey, data);
      final SignResponse response = in.readSignResponse();
      final Iterator iterator = in.readSignResponseData(response);

      final byte[] responseType = iterator.next();

      // TODO (dxia) Support other SSH keys
      final String signatureFormatId = new String(responseType);
      if (!signatureFormatId.equals(RSA.RSA_LABEL)) {
        throw new RuntimeException("I unexpectedly got a non-RSA signature format ID in the "
                                   + "SSH2_AGENT_SIGN_RESPONSE's signature blob.");
      }

      return iterator.next();
    } catch (IOException e) {
      throw Throwables.propagate(e);
    }
  }

  /**
   * Get the SSH public key from the ssh-agent based on fingerprint.
   * @param fingerprint {@link Fingerprint} of the SSH public key to find.
   * @return Corresponding {@link PublicKey}
   * @throws IOException
   */
  private PublicKey findKey(final Fingerprint fingerprint) throws IOException {
    out.requestIdentities();
    final IdentitiesAnswer answer = in.readIdentitiesAnswer();
    final Iterator keyIterator = in.readIdentitiesAnswerData(answer);

    while (keyIterator.hasNext()) {
      final RSAPublicKey key = keyIterator.next();
      if (fingerprint.matches(key)) {
        return key;
      }
    }

    // If we get here, it means there's no key with the requested finger print.
    return null;
  }

  @Override
  public void close() throws Exception {
    out.close();
    in.close();
  }

  @Override
  public String toString() {
    return Objects.toStringHelper(this)
        .toString();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy