mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-12 16:35:45 +01:00
start implementation of store api
This commit is contained in:
21
pom.xml
21
pom.xml
@@ -525,6 +525,26 @@
|
|||||||
<version>1.14</version>
|
<version>1.14</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- gpg -->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcpg-jdk15on</artifactId>
|
||||||
|
<version>${bouncycastle.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcprov-jdk15on</artifactId>
|
||||||
|
<version>${bouncycastle.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcpkix-jdk15on</artifactId>
|
||||||
|
<version>${bouncycastle.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
@@ -899,6 +919,7 @@
|
|||||||
<guice.version>4.2.3</guice.version>
|
<guice.version>4.2.3</guice.version>
|
||||||
<jaxb.version>2.3.3</jaxb.version>
|
<jaxb.version>2.3.3</jaxb.version>
|
||||||
<hibernate-validator.version>6.1.5.Final</hibernate-validator.version>
|
<hibernate-validator.version>6.1.5.Final</hibernate-validator.version>
|
||||||
|
<bouncycastle.version>1.65</bouncycastle.version>
|
||||||
|
|
||||||
<!-- event bus -->
|
<!-- event bus -->
|
||||||
<legman.version>1.6.2</legman.version>
|
<legman.version>1.6.2</legman.version>
|
||||||
|
|||||||
@@ -114,6 +114,23 @@
|
|||||||
<version>${jjwt.version}</version>
|
<version>${jjwt.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- gpg -->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcpg-jdk15on</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcprov-jdk15on</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcpkix-jdk15on</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- json -->
|
<!-- json -->
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
public class GPGException extends RuntimeException {
|
||||||
|
GPGException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
GPGException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
86
scm-webapp/src/main/java/sonia/scm/security/gpg/Keys.java
Normal file
86
scm-webapp/src/main/java/sonia/scm/security/gpg/Keys.java
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.bouncycastle.bcpg.ArmoredInputStream;
|
||||||
|
import org.bouncycastle.gpg.keybox.PublicKeyRingBlob;
|
||||||
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
|
import org.bouncycastle.openpgp.PGPObjectFactory;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||||
|
import org.bouncycastle.openpgp.PGPUtil;
|
||||||
|
import org.bouncycastle.openpgp.jcajce.JcaPGPPublicKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
|
||||||
|
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
|
||||||
|
import sonia.scm.security.PublicKey;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
final class Keys {
|
||||||
|
|
||||||
|
private static final KeyFingerPrintCalculator calculator = new JcaKeyFingerprintCalculator();
|
||||||
|
|
||||||
|
private Keys() {}
|
||||||
|
|
||||||
|
static String resolveIdFromKey(String rawKey) throws IOException, PGPException {
|
||||||
|
List<PGPPublicKey> keys = collectKeys(rawKey);
|
||||||
|
if (keys.size() > 1) {
|
||||||
|
keys = keys.stream().filter(PGPPublicKey::isMasterKey).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
if (keys.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("found multiple keys, but no master keys");
|
||||||
|
}
|
||||||
|
if (keys.size() > 1) {
|
||||||
|
throw new IllegalArgumentException("found multiple master keys");
|
||||||
|
}
|
||||||
|
|
||||||
|
PGPPublicKey pgpPublicKey = keys.get(0);
|
||||||
|
return createId(pgpPublicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String createId(PGPPublicKey pgpPublicKey) {
|
||||||
|
return "0x" + Long.toHexString(pgpPublicKey.getKeyID()).toUpperCase(Locale.ENGLISH);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<PGPPublicKey> collectKeys(String rawKey) throws IOException, PGPException {
|
||||||
|
List<PGPPublicKey> publicKeys = new ArrayList<>();
|
||||||
|
InputStream decoderStream = PGPUtil.getDecoderStream(new ByteArrayInputStream(rawKey.getBytes(StandardCharsets.UTF_8)));
|
||||||
|
PGPPublicKeyRingCollection collection = new PGPPublicKeyRingCollection(decoderStream, calculator);
|
||||||
|
for (PGPPublicKeyRing pgpPublicKeys : collection) {
|
||||||
|
for (PGPPublicKey pgpPublicKey : pgpPublicKeys) {
|
||||||
|
publicKeys.add(pgpPublicKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return publicKeys;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.google.common.annotations.VisibleForTesting;
|
||||||
|
import org.apache.shiro.SecurityUtils;
|
||||||
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
|
import sonia.scm.security.PublicKey;
|
||||||
|
import sonia.scm.store.DataStore;
|
||||||
|
import sonia.scm.store.DataStoreFactory;
|
||||||
|
|
||||||
|
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.XmlElement;
|
||||||
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class PublicKeyStore {
|
||||||
|
|
||||||
|
private static final String STORE_NAME = "gpg_public_keys";
|
||||||
|
|
||||||
|
private final DataStore<RawGpgKey> store;
|
||||||
|
private final Supplier<String> currentUserSupplier;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public PublicKeyStore(DataStoreFactory dataStoreFactory) {
|
||||||
|
this(
|
||||||
|
dataStoreFactory.withType(RawGpgKey.class).withName(STORE_NAME).build(),
|
||||||
|
() -> SecurityUtils.getSubject().getPrincipal().toString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
PublicKeyStore(DataStore<RawGpgKey> store, Supplier<String> currentUserSupplier) {
|
||||||
|
this.store = store;
|
||||||
|
this.currentUserSupplier = currentUserSupplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RawGpgKey add(String displayName, String rawKey) {
|
||||||
|
try {
|
||||||
|
String id = Keys.resolveIdFromKey(rawKey);
|
||||||
|
RawGpgKey key = new RawGpgKey(id, displayName, currentUserSupplier.get(), rawKey, Instant.now());
|
||||||
|
|
||||||
|
store.put(id, key);
|
||||||
|
|
||||||
|
return key;
|
||||||
|
} catch (IOException | PGPException e) {
|
||||||
|
throw new GPGException("failed to resolve id from gpg key");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<RawGpgKey> findById(String id) {
|
||||||
|
return store.getOptional(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* 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.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import sonia.scm.xml.XmlInstantAdapter;
|
||||||
|
|
||||||
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
public class RawGpgKey {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String displayName;
|
||||||
|
private String owner;
|
||||||
|
private String raw;
|
||||||
|
|
||||||
|
@XmlJavaTypeAdapter(XmlInstantAdapter.class)
|
||||||
|
private Instant created;
|
||||||
|
|
||||||
|
RawGpgKey(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
RawGpgKey that = (RawGpgKey) o;
|
||||||
|
return Objects.equals(id, that.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.google.common.io.Resources;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
final class GPGTestHelper {
|
||||||
|
|
||||||
|
private GPGTestHelper() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
|
static String readKey(String key) throws IOException {
|
||||||
|
URL resource = Resources.getResource("sonia/scm/security/gpg/" + key);
|
||||||
|
return Resources.toString(resource, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.bouncycastle.openpgp.PGPException;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static sonia.scm.security.gpg.GPGTestHelper.readKey;
|
||||||
|
|
||||||
|
class KeysTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveId() throws IOException, PGPException {
|
||||||
|
String rawPublicKey = readKey("single.asc");
|
||||||
|
assertThat(Keys.resolveIdFromKey(rawPublicKey)).isEqualTo("0x975922F193B07D6E");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveIdFromMasterKey() throws IOException, PGPException {
|
||||||
|
String rawPublicKey = readKey("subkeys.asc");
|
||||||
|
assertThat(Keys.resolveIdFromKey(rawPublicKey)).isEqualTo("0x13B13D4C8A9350A1");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import sonia.scm.store.InMemoryDataStore;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class PublicKeyStoreTest {
|
||||||
|
|
||||||
|
private PublicKeyStore keyStore;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUpKeyStore() {
|
||||||
|
keyStore = new PublicKeyStore(new InMemoryDataStore<>(), () -> "trillian");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnStoredKey() throws IOException {
|
||||||
|
String rawKey = GPGTestHelper.readKey("single.asc");
|
||||||
|
Instant now = Instant.now();
|
||||||
|
|
||||||
|
RawGpgKey key = keyStore.add("SCM Package Key", rawKey);
|
||||||
|
assertThat(key.getId()).isEqualTo("0x975922F193B07D6E");
|
||||||
|
assertThat(key.getDisplayName()).isEqualTo("SCM Package Key");
|
||||||
|
assertThat(key.getOwner()).isEqualTo("trillian");
|
||||||
|
assertThat(key.getCreated()).isAfterOrEqualTo(now);
|
||||||
|
assertThat(key.getRaw()).isEqualTo(rawKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldFindStoredKeyById() throws IOException {
|
||||||
|
String rawKey = GPGTestHelper.readKey("single.asc");
|
||||||
|
keyStore.add("SCM Package Key", rawKey);
|
||||||
|
Optional<RawGpgKey> key = keyStore.findById("0x975922F193B07D6E");
|
||||||
|
assertThat(key).isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
mQINBF69VQEBEADcnNUucubBBn0nEubrNV3SWh1CbiUyhLU6TEKSUbyB2gDcOrMR
|
||||||
|
4wAeqK0ar4cloIpS5YEtjT/qVpERzabbJe0NSfCwYblpdfoA0idcvi7Ssnczfr/i
|
||||||
|
1cF0gqmAjoDqAaSk7xHa/mxiEwAUAXGDWu4pCksT8gHDDx/7lIkHvPZs+VGyMM/O
|
||||||
|
NVc3QET0JMuKhBYJafSJkdUl3qjX9N6ykETQIbxSv0YLjZuAzQJggLoiMWZtDkrU
|
||||||
|
KuXh/bJja2xnzj9XtMmpkrIRAX1VYLlI7sJ17b1Tv/yIvaZ1akccRSFtx/kqcM86
|
||||||
|
LSO77E82EfLhuVeVCvzxzgfrYHVVX6oFhAEzKUI0TRT453yGZuC5j91HXP3VuXjH
|
||||||
|
opAODeQLbDfcWPH8joegDZBuK1dQRvVcyh5CMSAWHw9vgXKergrMHVB4EVIqMMir
|
||||||
|
Q29KfbSuhA5D+xLxPjphbDsMIcLMy0ADd7N0ydmpt2x7ES3Sx3iTssibBYQB731Z
|
||||||
|
DQCgxy8mdL4MSCNUkDziGyqK+cI9/jRehiOimFsnQIDqQ1hOQBw7M21lvGlbn1IA
|
||||||
|
iKosi9rb+tUtnNT2d3byjvjZzMj1xoJeOs9i+xEDu1224xEEfJIixfSLLW2T8Qdb
|
||||||
|
6/a5XGENQB9ZfcW0CrK+V+bHLKXkY2MG9mAL3KEgmDDqydQTwOGNFzJHpwARAQAB
|
||||||
|
tE9TQ00gUGFja2FnZXMgKHNpZ25pbmcga2V5IGZvciBwYWNrYWdlcy5zY20tbWFu
|
||||||
|
YWdlci5vcmcpIDxzY20tdGVhbUBjbG91ZG9ndS5jb20+iQJOBBMBCgA4FiEEI9Ji
|
||||||
|
WyNeJaRxmHWil1ki8ZOwfW4FAl69VQECGy8FCwkIBwMFFQoJCAsFFgIDAQACHgEC
|
||||||
|
F4AACgkQl1ki8ZOwfW49vg/9EYZSEejdfuzLWcC1M8C9lyausvB+SAI7fEcnD4do
|
||||||
|
w6WEdnPTus5aAnr1qOncH3aJpjwqfIpuCMdS94i9jgLJTLaQ8S2WegLFVhDQvC7v
|
||||||
|
Q6ZieOUAYVWJx2Klq2OT1MVJPEzskV3QtFBTaHmuseJrGvH0Waw26MGw8MiAPyES
|
||||||
|
oZGdcULZBwpr8nazqcFXFuDxMFr1Y28sEzW/ntfScLnIVIVXAWaAXq/4dtB1cIIc
|
||||||
|
KKsszkM018HdEPSf99ry/nqiwGkOBqMUiEM9+VIMuJRs+BSvT9ETM0yx21fYV2Jj
|
||||||
|
YG20ahsd1tRFwYLLyzwukT9KUBydZ2RZP+L0gkC+WrfMxvreQxP7d8PH8aF2Ii1e
|
||||||
|
SpLs91h97tXq+ucp6YyGTEsVnajQeGSA0mX/AhOe3swBNZ08vuhSWkKjKnOXqR4h
|
||||||
|
IyJaJGAuo6vd+GzdAu/9MxWZQZTWERauofxLTzESwJl7WfTgEFvF+7hNCkQmUA7r
|
||||||
|
oGc8ahEvuGCZG2MtfBPSPL51FifDlO+G0rifWqHuocZWdBX6fcmt+SYb8SHaG0cu
|
||||||
|
JP35uWVGuva+Bw73+S21xU3yIjt8bTkZpIuHO9xhivXIOVR3jVhB1V6KrI+jlZl/
|
||||||
|
DmQc8MI3+Ez6Hh3kVQjokL1W/u/gg8XumCmc+4hq2QIOSF/ODUMg0b1nne4msLc/
|
||||||
|
H1Q=
|
||||||
|
=5/Nh
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
122
scm-webapp/src/test/resources/sonia/scm/security/gpg/subkeys.asc
Normal file
122
scm-webapp/src/test/resources/sonia/scm/security/gpg/subkeys.asc
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
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
|
||||||
|
=dKpI
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
Reference in New Issue
Block a user