package jenkins.model.identity; import hudson.Extension; import hudson.model.UnprotectedRootAction; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import org.apache.commons.codec.Charsets; import org.apache.commons.codec.binary.Base64; /** * A simple root action that exposes the public key to users so that they do not need to search for the * {@code X-Instance-Identity} response header, also exposes the fingerprint of the public key so that people * can verify a fingerprint of a master before connecting to it. * * @since 2.16 */ @Extension public class IdentityRootAction implements UnprotectedRootAction { /** * {@inheritDoc} */ @Override public String getIconFileName() { return null; } /** * {@inheritDoc} */ @Override public String getDisplayName() { return null; } /** * {@inheritDoc} */ @Override public String getUrlName() { return InstanceIdentityProvider.RSA.getKeyPair() == null ? null : "instance-identity"; } /** * Returns the PEM encoded public key. * * @return the PEM encoded public key. */ public String getPublicKey() { RSAPublicKey key = InstanceIdentityProvider.RSA.getPublicKey(); if (key == null) { return null; } byte[] encoded = Base64.encodeBase64(key.getEncoded()); int index = 0; StringBuilder buf = new StringBuilder(encoded.length + 20); while (index < encoded.length) { int len = Math.min(64, encoded.length - index); if (index > 0) { buf.append("\n"); } buf.append(new String(encoded, index, len, Charsets.UTF_8)); index += len; } return String.format("-----BEGIN PUBLIC KEY-----%n%s%n-----END PUBLIC KEY-----%n", buf.toString()); } /** * Returns the fingerprint of the public key. * * @return the fingerprint of the public key. */ public String getFingerprint() { RSAPublicKey key = InstanceIdentityProvider.RSA.getPublicKey(); if (key == null) { return null; } // TODO replace with org.jenkinsci.remoting.util.KeyUtils once JENKINS-36871 changes are merged try { MessageDigest digest = MessageDigest.getInstance("MD5"); digest.reset(); byte[] bytes = digest.digest(key.getEncoded()); StringBuilder result = new StringBuilder(Math.max(0, bytes.length * 3 - 1)); for (int i = 0; i < bytes.length; i++) { if (i > 0) { result.append(':'); } int b = bytes[i] & 0xFF; result.append(Character.forDigit((b>>4)&0x0f, 16)).append(Character.forDigit(b&0xf, 16)); } return result.toString(); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("JLS mandates MD5 support"); } } }