This commit is contained in:
Konstantin Schaper
2020-08-03 16:09:37 +02:00
parent 4768e7a1be
commit 08a025ba81
12 changed files with 278 additions and 11 deletions

View File

@@ -24,20 +24,36 @@
package sonia.scm.security.gpg;
import com.sun.tools.javac.util.Pair;
import org.apache.shiro.SecurityUtils;
import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPKeyRingGenerator;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.security.GPG;
import sonia.scm.security.PrivateKey;
import sonia.scm.security.PublicKey;
import sonia.scm.security.SessionId;
import sonia.scm.user.User;
import javax.inject.Inject;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -45,11 +61,13 @@ import java.util.stream.Collectors;
public class DefaultGPG implements GPG {
private static final Logger LOG = LoggerFactory.getLogger(DefaultGPG.class);
private final PublicKeyStore store;
private final PublicKeyStore publicKeyStore;
private final PrivateKeyStore privateKeyStore;
@Inject
public DefaultGPG(PublicKeyStore store) {
this.store = store;
public DefaultGPG(PublicKeyStore publicKeyStore, PrivateKeyStore privateKeyStore) {
this.publicKeyStore = publicKeyStore;
this.privateKeyStore = privateKeyStore;
}
@Override
@@ -67,14 +85,14 @@ public class DefaultGPG implements GPG {
@Override
public Optional<PublicKey> findPublicKey(String id) {
Optional<RawGpgKey> key = store.findById(id);
Optional<RawGpgKey> key = publicKeyStore.findById(id);
return key.map(rawGpgKey -> new GpgKey(rawGpgKey.getId(), rawGpgKey.getOwner(), rawGpgKey.getRaw(), rawGpgKey.getContacts()));
}
@Override
public Iterable<PublicKey> findPublicKeysByUsername(String username) {
List<RawGpgKey> keys = store.findByUsername(username);
List<RawGpgKey> keys = publicKeyStore.findByUsername(username);
if (!keys.isEmpty()) {
return keys
@@ -88,6 +106,36 @@ public class DefaultGPG implements GPG {
@Override
public PrivateKey getPrivateKey() {
throw new UnsupportedOperationException("getPrivateKey is not yet implemented");
final String userId = SecurityUtils.getSubject().getPrincipal().toString();
final Optional<String> privateRawKey = privateKeyStore.getForUserId(userId);
if (!privateRawKey.isPresent()) {
try {
// Generate key pair
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
keyPairGenerator.initialize(2048);
KeyPair pair = keyPairGenerator.generateKeyPair();
String identity = "0xAWESOMExBOB";
PGPKeyPair keyPair = new JcaPGPKeyPair(PGPPublicKey.RSA_GENERAL, pair, new Date());
new PGPKeyRingGenerator().generateSecretKeyRing().;
} catch (PGPException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
}
//
privateKeyStore.setForUserId(user.getId(), privateKeyGpgKeyPair.fst);
publicKeyStore.add(user.getDisplayName(), user.getName(), privateKeyGpgKeyPair.snd.getRaw());
return privateKeyGpgKeyPair.fst;
} else {
// PGPUtil.getDecoderStream();
return privateRawKey.get();
}
}
}

View File

@@ -0,0 +1,78 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package sonia.scm.security.gpg;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import sonia.scm.security.CipherUtil;
import sonia.scm.security.PrivateKey;
import sonia.scm.store.DataStore;
import sonia.scm.store.DataStoreFactory;
import sonia.scm.xml.XmlInstantAdapter;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.time.Instant;
import java.util.Optional;
@Singleton
class PrivateKeyStore {
private static final String STORE_NAME = "gpg_private_keys";
private final DataStore<RawPrivateKey> store;
@Inject
PrivateKeyStore(DataStoreFactory dataStoreFactory) {
this.store = dataStoreFactory.withType(RawPrivateKey.class).withName(STORE_NAME).build();
}
Optional<String> getForUserId(String userId) {
return store.getOptional(userId).map(rawPrivateKey -> CipherUtil.getInstance().decode(rawPrivateKey.key));
}
void setForUserId(String userId, String rawKey) {
final String encodedRawKey = CipherUtil.getInstance().encode(rawKey);
store.put(userId, new RawPrivateKey(encodedRawKey, Instant.now()));
}
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@AllArgsConstructor
@NoArgsConstructor
private class RawPrivateKey {
private String key;
@XmlJavaTypeAdapter(XmlInstantAdapter.class)
private Instant date;
}
}

View File

@@ -58,7 +58,7 @@ public abstract class PublicKeyMapper {
RawGpgKeyDto createDto(RawGpgKey rawGpgKey) {
Links.Builder linksBuilder = linkingTo();
linksBuilder.self(createSelfLink(rawGpgKey));
if (UserPermissions.changePublicKeys(rawGpgKey.getOwner()).isPermitted()) {
if (UserPermissions.changePublicKeys(rawGpgKey.getOwner()).isPermitted() && !rawGpgKey.isReadonly()) {
linksBuilder.single(Link.link("delete", createDeleteLink(rawGpgKey)));
}
return new RawGpgKeyDto(linksBuilder.build());

View File

@@ -30,12 +30,14 @@ import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import sonia.scm.api.v2.resources.ErrorDto;
import sonia.scm.security.AllowAnonymousAccess;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
@@ -97,6 +99,7 @@ public class PublicKeyResource {
@GET
@Path("{id}")
@Produces(MEDIA_TYPE)
@AllowAnonymousAccess
@Operation(
summary = "Get single key for user",
description = "Returns a single public key for username by id.",
@@ -129,7 +132,7 @@ public class PublicKeyResource {
schema = @Schema(implementation = ErrorDto.class)
)
)
public Response findById(@PathParam("id") String id) {
public Response findByIdJson(@PathParam("id") String id) {
Optional<RawGpgKey> byId = store.findById(id);
if (byId.isPresent()) {
return Response.ok(mapper.map(byId.get())).build();

View File

@@ -64,7 +64,7 @@ public class PublicKeyStore {
this.eventBus = eventBus;
}
public RawGpgKey add(String displayName, String username, String rawKey) {
public RawGpgKey add(String displayName, String username, String rawKey, ) {
UserPermissions.changePublicKeys(username).check();
if (!rawKey.contains("PUBLIC KEY")) {

View File

@@ -50,6 +50,7 @@ public class RawGpgKey {
private String displayName;
private String owner;
private String raw;
private boolean readonly = false;
private Set<Person> contacts;
@XmlJavaTypeAdapter(XmlInstantAdapter.class)