You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

JGitPublicKeyAuthentication.java 7.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. /*
  2. * Copyright (C) 2018, 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.internal.transport.sshd;
  11. import java.io.IOException;
  12. import java.security.PublicKey;
  13. import java.security.spec.InvalidKeySpecException;
  14. import java.text.MessageFormat;
  15. import java.util.Deque;
  16. import java.util.HashSet;
  17. import java.util.LinkedList;
  18. import java.util.List;
  19. import java.util.Set;
  20. import org.apache.sshd.client.auth.pubkey.UserAuthPublicKey;
  21. import org.apache.sshd.client.session.ClientSession;
  22. import org.apache.sshd.common.NamedFactory;
  23. import org.apache.sshd.common.NamedResource;
  24. import org.apache.sshd.common.RuntimeSshException;
  25. import org.apache.sshd.common.SshConstants;
  26. import org.apache.sshd.common.config.keys.KeyUtils;
  27. import org.apache.sshd.common.signature.Signature;
  28. import org.apache.sshd.common.signature.SignatureFactoriesHolder;
  29. import org.apache.sshd.common.util.buffer.Buffer;
  30. /**
  31. * Custom {@link UserAuthPublicKey} implementation fixing SSHD-1105: if there
  32. * are several signature algorithms applicable for a public key type, we must
  33. * try them all, in the correct order.
  34. *
  35. * @see <a href="https://issues.apache.org/jira/browse/SSHD-1105">SSHD-1105</a>
  36. * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=572056">Bug
  37. * 572056</a>
  38. */
  39. public class JGitPublicKeyAuthentication extends UserAuthPublicKey {
  40. private final Deque<String> currentAlgorithms = new LinkedList<>();
  41. private String chosenAlgorithm;
  42. JGitPublicKeyAuthentication(List<NamedFactory<Signature>> factories) {
  43. super(factories);
  44. }
  45. @Override
  46. protected boolean sendAuthDataRequest(ClientSession session, String service)
  47. throws Exception {
  48. if (current == null) {
  49. currentAlgorithms.clear();
  50. chosenAlgorithm = null;
  51. }
  52. String currentAlgorithm = null;
  53. if (current != null && !currentAlgorithms.isEmpty()) {
  54. currentAlgorithm = currentAlgorithms.poll();
  55. if (chosenAlgorithm != null) {
  56. Set<String> knownServerAlgorithms = session.getAttribute(
  57. JGitKexExtensionHandler.SERVER_ALGORITHMS);
  58. if (knownServerAlgorithms != null
  59. && knownServerAlgorithms.contains(chosenAlgorithm)) {
  60. // We've tried key 'current' with 'chosenAlgorithm', but it
  61. // failed. However, the server had told us it supported
  62. // 'chosenAlgorithm'. Thus it makes no sense to continue
  63. // with this key and other signature algorithms. Skip to the
  64. // next key, if any.
  65. currentAlgorithm = null;
  66. }
  67. }
  68. }
  69. if (currentAlgorithm == null) {
  70. try {
  71. if (keys == null || !keys.hasNext()) {
  72. if (log.isDebugEnabled()) {
  73. log.debug(
  74. "sendAuthDataRequest({})[{}] no more keys to send", //$NON-NLS-1$
  75. session, service);
  76. }
  77. current = null;
  78. return false;
  79. }
  80. current = keys.next();
  81. currentAlgorithms.clear();
  82. chosenAlgorithm = null;
  83. } catch (Error e) { // Copied from superclass
  84. throw new RuntimeSshException(e);
  85. }
  86. }
  87. PublicKey key;
  88. try {
  89. key = current.getPublicKey();
  90. } catch (Error e) { // Copied from superclass
  91. throw new RuntimeSshException(e);
  92. }
  93. if (currentAlgorithm == null) {
  94. String keyType = KeyUtils.getKeyType(key);
  95. Set<String> aliases = new HashSet<>(
  96. KeyUtils.getAllEquivalentKeyTypes(keyType));
  97. aliases.add(keyType);
  98. List<NamedFactory<Signature>> existingFactories;
  99. if (current instanceof SignatureFactoriesHolder) {
  100. existingFactories = ((SignatureFactoriesHolder) current)
  101. .getSignatureFactories();
  102. } else {
  103. existingFactories = getSignatureFactories();
  104. }
  105. if (existingFactories != null) {
  106. if (log.isDebugEnabled()) {
  107. log.debug(
  108. "sendAuthDataRequest({})[{}] selecting from PubKeyAcceptedAlgorithms {}", //$NON-NLS-1$
  109. session, service,
  110. NamedResource.getNames(existingFactories));
  111. }
  112. // Select the factories by name and in order
  113. existingFactories.forEach(f -> {
  114. if (aliases.contains(f.getName())) {
  115. currentAlgorithms.add(f.getName());
  116. }
  117. });
  118. }
  119. currentAlgorithm = currentAlgorithms.isEmpty() ? keyType
  120. : currentAlgorithms.poll();
  121. }
  122. String name = getName();
  123. if (log.isDebugEnabled()) {
  124. log.debug(
  125. "sendAuthDataRequest({})[{}] send SSH_MSG_USERAUTH_REQUEST request {} type={} - fingerprint={}", //$NON-NLS-1$
  126. session, service, name, currentAlgorithm,
  127. KeyUtils.getFingerPrint(key));
  128. }
  129. chosenAlgorithm = currentAlgorithm;
  130. Buffer buffer = session
  131. .createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST);
  132. buffer.putString(session.getUsername());
  133. buffer.putString(service);
  134. buffer.putString(name);
  135. buffer.putBoolean(false);
  136. buffer.putString(currentAlgorithm);
  137. buffer.putPublicKey(key);
  138. session.writePacket(buffer);
  139. return true;
  140. }
  141. @Override
  142. protected boolean processAuthDataRequest(ClientSession session,
  143. String service, Buffer buffer) throws Exception {
  144. String name = getName();
  145. int cmd = buffer.getUByte();
  146. if (cmd != SshConstants.SSH_MSG_USERAUTH_PK_OK) {
  147. throw new IllegalStateException(MessageFormat.format(
  148. SshdText.get().pubkeyAuthWrongCommand,
  149. SshConstants.getCommandMessageName(cmd),
  150. session.getConnectAddress(), session.getServerVersion()));
  151. }
  152. PublicKey key;
  153. try {
  154. key = current.getPublicKey();
  155. } catch (Error e) { // Copied from superclass
  156. throw new RuntimeSshException(e);
  157. }
  158. String rspKeyAlgorithm = buffer.getString();
  159. PublicKey rspKey = buffer.getPublicKey();
  160. if (log.isDebugEnabled()) {
  161. log.debug(
  162. "processAuthDataRequest({})[{}][{}] SSH_MSG_USERAUTH_PK_OK type={}, fingerprint={}", //$NON-NLS-1$
  163. session, service, name, rspKeyAlgorithm,
  164. KeyUtils.getFingerPrint(rspKey));
  165. }
  166. if (!KeyUtils.compareKeys(rspKey, key)) {
  167. throw new InvalidKeySpecException(MessageFormat.format(
  168. SshdText.get().pubkeyAuthWrongKey,
  169. KeyUtils.getFingerPrint(key),
  170. KeyUtils.getFingerPrint(rspKey),
  171. session.getConnectAddress(), session.getServerVersion()));
  172. }
  173. if (!chosenAlgorithm.equalsIgnoreCase(rspKeyAlgorithm)) {
  174. // 'algo' SHOULD be the same as 'chosenAlgorithm', which is the one
  175. // we sent above. See https://tools.ietf.org/html/rfc4252#page-9 .
  176. //
  177. // However, at least Github (SSH-2.0-babeld-383743ad) servers seem
  178. // to return the key type, not the algorithm name.
  179. //
  180. // So we don't check but just log the inconsistency. We sign using
  181. // 'chosenAlgorithm' in any case, so we don't really care what the
  182. // server says here.
  183. log.warn(MessageFormat.format(
  184. SshdText.get().pubkeyAuthWrongSignatureAlgorithm,
  185. chosenAlgorithm, rspKeyAlgorithm, session.getConnectAddress(),
  186. session.getServerVersion()));
  187. }
  188. String username = session.getUsername();
  189. Buffer out = session
  190. .createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST);
  191. out.putString(username);
  192. out.putString(service);
  193. out.putString(name);
  194. out.putBoolean(true);
  195. out.putString(chosenAlgorithm);
  196. out.putPublicKey(key);
  197. if (log.isDebugEnabled()) {
  198. log.debug(
  199. "processAuthDataRequest({})[{}][{}]: signing with algorithm {}", //$NON-NLS-1$
  200. session, service, name, chosenAlgorithm);
  201. }
  202. appendSignature(session, service, name, username, chosenAlgorithm, key,
  203. out);
  204. session.writePacket(out);
  205. return true;
  206. }
  207. @Override
  208. protected void releaseKeys() throws IOException {
  209. currentAlgorithms.clear();
  210. current = null;
  211. chosenAlgorithm = null;
  212. super.releaseKeys();
  213. }
  214. }