Commit 0229665f authored by michael.simon's avatar michael.simon
Browse files

Merge branch '72-let-user-deploy-their-ssh-public-keys' into 'branch-2.6'

Resolve "Let User deploy their SSH Public Keys"

Closes #72

See merge request simon/reg-app!9
parents fbb06c9f 117a41ff
package edu.kit.scc.webreg.ssh;
import java.io.Serializable;
import java.math.BigInteger;
import java.security.AlgorithmParameters;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.RSAPublicKeySpec;
import javax.enterprise.context.ApplicationScoped;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
@ApplicationScoped
public class OpenSshKeyDecoder implements Serializable {
private static final long serialVersionUID = 1L;
public OpenSshPublicKey decode(String name, String opensshPublicKey) throws UnsupportedKeyTypeException {
OpenSshPublicKey key = new OpenSshPublicKey();
key.setName(name);
key.setValue(opensshPublicKey.trim());
return decode(key);
}
public OpenSshPublicKey decode(OpenSshPublicKey key) throws UnsupportedKeyTypeException {
getKeyBytes(key);
try {
String type = decodeType(key);
if (type.equals("ssh-rsa")) {
BigInteger e = decodeBigInt(key);
BigInteger m = decodeBigInt(key);
RSAPublicKeySpec spec = new RSAPublicKeySpec(m, e);
key.setPublicKey(KeyFactory.getInstance("RSA").generatePublic(spec));
} else if (type.equals("ssh-dss")) {
BigInteger p = decodeBigInt(key);
BigInteger q = decodeBigInt(key);
BigInteger g = decodeBigInt(key);
BigInteger y = decodeBigInt(key);
DSAPublicKeySpec spec = new DSAPublicKeySpec(y, p, q, g);
key.setPublicKey(KeyFactory.getInstance("DSA").generatePublic(spec));
} else if (type.startsWith("ecdsa-sha2-") &&
(type.endsWith("nistp256") || type.endsWith("nistp384") || type.endsWith("nistp521"))) {
// Based on RFC 5656, section 3.1 (https://tools.ietf.org/html/rfc5656#section-3.1)
String identifier = decodeType(key);
BigInteger q = decodeBigInt(key);
ECPoint ecPoint = getECPoint(q, identifier);
ECParameterSpec ecParameterSpec = getECParameterSpec(identifier);
ECPublicKeySpec spec = new ECPublicKeySpec(ecPoint, ecParameterSpec);
key.setPublicKey(KeyFactory.getInstance("EC").generatePublic(spec));
} else {
key.setDecoderResult("Unsupported key type");
}
return key;
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
key.setDecoderResult("Unable to decode public key");
return key;
}
}
private void getKeyBytes(OpenSshPublicKey key) throws UnsupportedKeyTypeException {
for (String part : key.getValue().split(" ")) {
if (Base64.isBase64(part) && part.startsWith("AAAA")) {
key.setBaseDate(part);
key.setBytes(Base64.decodeBase64(part));
return;
}
}
throw new UnsupportedKeyTypeException("no Base64 part to decode");
}
private String decodeType(OpenSshPublicKey key) {
int len = decodeInt(key);
String type = new String(key.getBytes(), key.getDecoderPos(), len);
key.increaseDecoderPos(len);
return type;
}
private int decodeInt(OpenSshPublicKey key) {
byte[] bytes = key.getBytes();
int pos = key.getDecoderPos();
int header = ((bytes[pos] & 0xFF) << 24) | ((bytes[pos+1] & 0xFF) << 16)
| ((bytes[pos+2] & 0xFF) << 8) | (bytes[pos+3] & 0xFF);
key.increaseDecoderPos(4);
return header;
}
private BigInteger decodeBigInt(OpenSshPublicKey key) {
int len = decodeInt(key);
byte[] bigIntBytes = new byte[len];
System.arraycopy(key.getBytes(), key.getDecoderPos(), bigIntBytes, 0, len);
key.increaseDecoderPos(len);
return new BigInteger(bigIntBytes);
}
ECPoint getECPoint(BigInteger q, String identifier) {
String name = identifier.replace("nist", "sec") + "r1";
ECNamedCurveParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec(name);
org.bouncycastle.math.ec.ECPoint point = ecSpec.getCurve().decodePoint(q.toByteArray());
BigInteger x = point.getAffineXCoord().toBigInteger();
BigInteger y = point.getAffineYCoord().toBigInteger();
return new ECPoint(x, y);
}
ECParameterSpec getECParameterSpec(String identifier) throws UnsupportedKeyTypeException {
try {
// http://www.bouncycastle.org/wiki/pages/viewpage.action?pageId=362269#SupportedCurves(ECDSAandECGOST)-NIST(aliasesforSECcurves)
String name = identifier.replace("nist", "sec") + "r1";
AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC");
parameters.init(new ECGenParameterSpec(name));
return parameters.getParameterSpec(ECParameterSpec.class);
} catch (InvalidParameterSpecException | NoSuchAlgorithmException e) {
throw new UnsupportedKeyTypeException("Unable to get parameter spec for identifier " + identifier, e);
}
}
}
package edu.kit.scc.webreg.ssh;
import java.security.PublicKey;
import com.fasterxml.jackson.annotation.JsonIgnore;
public class OpenSshPublicKey {
private String name;
private String value;
@JsonIgnore
private byte[] bytes;
@JsonIgnore
private int decoderPos;
@JsonIgnore
private PublicKey publicKey;
@JsonIgnore
private String baseDate;
@JsonIgnore
private String decoderResult;
public OpenSshPublicKey() {
super();
decoderPos = 0;
}
public byte[] getBytes() {
return bytes;
}
public void setBytes(byte[] bytes) {
this.bytes = bytes;
}
public int getDecoderPos() {
return decoderPos;
}
public void setDecoderPos(int decoderPos) {
this.decoderPos = decoderPos;
}
public void increaseDecoderPos(int steps) {
this.decoderPos += steps;
}
public PublicKey getPublicKey() {
return publicKey;
}
public void setPublicKey(PublicKey publicKey) {
this.publicKey = publicKey;
}
public String getBaseDate() {
return baseDate;
}
public void setBaseDate(String baseDate) {
this.baseDate = baseDate;
}
public String getDecoderResult() {
return decoderResult;
}
public void setDecoderResult(String decoderResult) {
this.decoderResult = decoderResult;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
package edu.kit.scc.webreg.ssh;
public class UnsupportedKeyTypeException extends Exception {
private static final long serialVersionUID = 1L;
public UnsupportedKeyTypeException() {
super();
}
public UnsupportedKeyTypeException(String arg0, Throwable arg1) {
super(arg0, arg1);
}
public UnsupportedKeyTypeException(String arg0) {
super(arg0);
}
public UnsupportedKeyTypeException(Throwable arg0) {
super(arg0);
}
}
/*******************************************************************************
* Copyright (c) 2014 Michael Simon.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*
* Contributors:
* Michael Simon - initial
******************************************************************************/
package edu.kit.scc.webreg.bean;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.faces.event.ComponentSystemEvent;
import javax.inject.Inject;
import org.slf4j.Logger;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import edu.kit.scc.webreg.entity.UserEntity;
import edu.kit.scc.webreg.service.UserService;
import edu.kit.scc.webreg.session.SessionManager;
import edu.kit.scc.webreg.ssh.OpenSshKeyDecoder;
import edu.kit.scc.webreg.ssh.OpenSshPublicKey;
import edu.kit.scc.webreg.ssh.UnsupportedKeyTypeException;
import edu.kit.scc.webreg.util.FacesMessageGenerator;
@ManagedBean
@ViewScoped
public class UserSshKeyManagementBean implements Serializable {
private static final long serialVersionUID = 1L;
private UserEntity user;
@Inject
private Logger logger;
@Inject
private UserService userService;
@Inject
private SessionManager sessionManager;
@Inject
private OpenSshKeyDecoder keyDecoder;
@Inject
private FacesMessageGenerator messageGenerator;
private List<OpenSshPublicKey> keyList;
private String newKey;
private String newName;
private OpenSshPublicKey selectedKey;
public void preRenderView(ComponentSystemEvent ev) {
if (user == null) {
user = userService.findByIdWithStore(sessionManager.getUserId());
keyList = new ArrayList<>();
if (user.getGenericStore().containsKey("ssh_key")) {
ObjectMapper om = new ObjectMapper();
try {
List<OpenSshPublicKey> tempKeyList = om.readValue(user.getGenericStore().get("ssh_key"),
new TypeReference<List<OpenSshPublicKey>>(){});
for (OpenSshPublicKey sshKey : tempKeyList) {
try {
keyList.add(keyDecoder.decode(sshKey));
} catch (UnsupportedKeyTypeException e) {
logger.warn("Unsupported key exception: ", e.getMessage());
}
}
} catch (IOException e) {
logger.warn("Could not read SSH keys from user: " + e.getMessage());
messageGenerator.addResolvedErrorMessage("error_msg", "SSH Key not readable. Resetting keys.", false);
}
}
}
}
public void deleteKey(String name) {
int removeIndex = -1;
for (int i=0; i<keyList.size(); i++) {
if (keyList.get(i).getName().equals(name)) {
removeIndex = i;
break;
}
}
if (removeIndex != -1) {
keyList.remove(removeIndex);
}
user.getGenericStore().put("ssh_key", buildSshKeyString());
user = userService.save(user);
messageGenerator.addResolvedInfoMessage("info", "ssh_key_deleted", false);
}
public void deployKey() {
OpenSshPublicKey key;
try {
key = keyDecoder.decode(newName, newKey);
keyList.add(key);
user.getGenericStore().put("ssh_key", buildSshKeyString());
user = userService.save(user);
newKey = "";
newName = "";
if (key.getPublicKey() == null) {
messageGenerator.addResolvedWarningMessage("warning", "ssh_key_unknown_format", false);
}
else {
messageGenerator.addResolvedInfoMessage("info", "ssh_key_deployed", false);
}
} catch (UnsupportedKeyTypeException e) {
logger.warn("An error occured whilst deploying key: " + e.getMessage());
messageGenerator.addResolvedErrorMessage("error_msg", e.toString(), false);
}
}
private String buildSshKeyString() {
ObjectMapper om = new ObjectMapper();
ArrayNode array = om.createArrayNode();
for (OpenSshPublicKey sshKey : keyList) {
array.add(om.convertValue(sshKey, JsonNode.class));
}
return array.toString();
}
public UserEntity getUser() {
return user;
}
public String getNewKey() {
return newKey;
}
public void setNewKey(String newKey) {
this.newKey = newKey;
}
public List<OpenSshPublicKey> getKeyList() {
return keyList;
}
public void setKeyList(List<OpenSshPublicKey> keyList) {
this.keyList = keyList;
}
public String getNewName() {
return newName;
}
public void setNewName(String newName) {
this.newName = newName;
}
public OpenSshPublicKey getSelectedKey() {
return selectedKey;
}
public void setSelectedKey(OpenSshPublicKey selectedKey) {
this.selectedKey = selectedKey;
}
}
......@@ -222,6 +222,7 @@ my_data_delete_all_text=Alle Daten mit Personenbezug werden aus dem System gel\u
my_data_delete_all_commit=Meine Daten jetzt l\u00F6schen
my_data_deleted_header=Daten wurden gel\u00F6scht
my_data_deleted_text=Ihre pers\u00F6nlichen Daten wurden aus dem System gel\u00F6scht.
my_ssh_keys=Meine SSH Pubkeys
nameid_format=NameID Format
name=Name
new_approvals=Neue Approvals
......@@ -332,6 +333,9 @@ single_sign_on=Single Sign On
singleton_scheduler_status=Status des Singleton Schedulers
sns_guest_reg=Sync&Share Gast Registrierung
sp_config=SP Konfiguration
ssh_key=SSH Key
ssh_key_name=SSH Key Name
ssh_key_type_unknown=Unbekannter SSH Key Typ
ssl-certificate-problem=Es besteht ein Problem mit dem SSL Zertifikat der Gegenstelle.
standard_scheduler_status=Status des Standard Schedulers
statistics=Statistiken
......
......@@ -241,6 +241,7 @@ my_data_delete_all_text=All personal data will be deleted from this system. All
my_data_delete_all_commit=Delete my personal data now
my_data_deleted_header=Data deleted
my_data_deleted_text=Your personal data has been deleted from this system.
my_ssh_keys=My SSH Pubkeys
nameid_format=NameID Format
name=Name
nameid_format=NameID format
......@@ -352,6 +353,9 @@ single_sign_on=Single sign on
sns_guest_reg=Sync&Share guest registration
sp_config=Service provider
sp_entities=Service providers
ssh_key=SSH Key
ssh_key_name=SSH Key Name
ssh_key_type_unknown=Unbekannter SSH Key Typ
ssl-certificate-problem=There is a problem with the peer SSL certificate.
standard_scheduler_status=Standard scheduler state
standby=Standby
......
......@@ -30,6 +30,11 @@
<h:link outcome="#{request.contextPath}/user/attribute-sources.xhtml" value="#{messages.my_attribute_sources}" />
</li>
</h:panelGroup>
<h:panelGroup rendered="#{authorizationBean.appConfig.getConfigValue('show_ssh_keys').equalsIgnoreCase('true')}">
<li><span class="ui-icon ui-icon-suitcase" style="display:inline-block; vertical-align: bottom;" />
<h:link outcome="#{request.contextPath}/user/ssh-keys.xhtml" value="#{messages.my_ssh_keys}" />
</li>
</h:panelGroup>
</ul>
</div>
</div>
......
......@@ -30,6 +30,11 @@
<h:link outcome="#{request.contextPath}/user/attribute-sources.xhtml" value="#{messages.my_attribute_sources}" />
</li>
</h:panelGroup>
<h:panelGroup rendered="#{authorizationBean.appConfig.getConfigValue('show_ssh_keys').equalsIgnoreCase('true')}">
<li><span class="ui-icon ui-icon-suitcase" style="display:inline-block; vertical-align: bottom;" />
<h:link outcome="#{request.contextPath}/user/ssh-keys.xhtml" value="#{messages.my_ssh_keys}" />
</li>
</h:panelGroup>
</ul>
</div>
</div>
......
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:bw="http://www.scc.kit.edu/bwfacelets"
xmlns:p="http://primefaces.org/ui"
xmlns:of="http://omnifaces.org/functions">
<head>
<title></title>
</head>
<body>
<f:view>
<f:metadata>
<f:event type="javax.faces.event.PreRenderViewEvent"
listener="#{userSshKeyManagementBean.preRenderView}" />
</f:metadata>
<ui:composition template="/template/default.xhtml">
<ui:param name="title" value="#{messages.title}"/>
<ui:define name="content">
<h:form id="form">
<p:panel id="panel" header="#{messages.my_ssh_keys}">
<div><p:messages showDetail="true" /></div>
<p:dataTable var="key" value="#{userSshKeyManagementBean.keyList.toArray()}">
<p:column>
<f:facet name="header"><h:outputText value="#{messages.ssh_key_name}:"/></f:facet>
<h:outputText value="#{key.name}"/>
</p:column>
<p:column>
<h:outputText value="#{key.publicKey}" rendered="#{not empty key.publicKey}" />
<h:outputText value="#{messages.ssh_key_type_unknown}" rendered="#{empty key.publicKey}" />
</p:column>
<p:column>
<p:commandLink id="delete" action="#{userSshKeyManagementBean.deleteKey(key.name)}" value="#{messages.delete}" immediate="true" update="@form">
<p:confirm header="#{messages.confirm_header}" message="#{messages.confirm}" />
</p:commandLink>
</p:column>
</p:dataTable>
<p:panelGrid id="baseData" columns="2">
<bw:inputText id="sshKeyName" label="#{messages.ssh_key_name}:"
value="#{userSshKeyManagementBean.newName}" required="true"/>
<h:outputText value="#{messages.ssh_key}:"/>
<p:inputTextarea value="#{userSshKeyManagementBean.newKey}" style="width: 400px; height: 100px;"
autoResize="false" />
</p:panelGrid>
<p:commandButton id="add" action="#{userSshKeyManagementBean.deployKey()}" value="#{messages.add}" update="@form" />
<p:ajaxStatus id="ajaxStatusPanel" style="display: inline-block; margin-left: 8px; vertical-align: baseline;">
<f:facet name="start">
<h:graphicImage value="#{resource['/img/ajax-loader.gif']}" alt="#{messages.loading}"/>
</f:facet>
<f:facet name="complete">
<h:outputText value="" />
</f:facet>
</p:ajaxStatus>
</p:panel>
<p:confirmDialog global="true" showEffect="fade" hideEffect="fade">
<p:commandButton value="#{messages.yes}" type="button" styleClass="ui-confirmdialog-yes" />
<p:commandButton value="#{messages.no}" type="button" styleClass="ui-confirmdialog-no" />
</p:confirmDialog>
</h:form>
</ui:define>
</ui:composition>
</f:view>
</body>
</html>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment