mirror of
https://github.com/theonedev/onedev.git
synced 2025-12-08 18:26:30 +00:00
chore: add unit test for signature verification
This commit is contained in:
parent
b6dfae7098
commit
96bde2706b
@ -9,7 +9,7 @@ import io.onedev.server.persistence.dao.EntityManager;
|
|||||||
public interface GpgKeyManager extends EntityManager<GpgKey> {
|
public interface GpgKeyManager extends EntityManager<GpgKey> {
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
GpgSigningKey findSignatureVerificationKey(long keyId);
|
GpgSigningKey findSigningKey(long keyId);
|
||||||
|
|
||||||
void create(GpgKey gpgKey);
|
void create(GpgKey gpgKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import io.onedev.server.persistence.dao.EntityManager;
|
|||||||
public interface SshKeyManager extends EntityManager<SshKey> {
|
public interface SshKeyManager extends EntityManager<SshKey> {
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
SshKey findByDigest(String digest);
|
SshKey findByFingerprint(String fingerprint);
|
||||||
|
|
||||||
void syncSshKeys(User user, Collection<String> sshKeys);
|
void syncSshKeys(User user, Collection<String> sshKeys);
|
||||||
|
|
||||||
|
|||||||
@ -96,7 +96,7 @@ public class DefaultGpgKeyManager extends BaseEntityManager<GpgKey> implements G
|
|||||||
|
|
||||||
@Sessional
|
@Sessional
|
||||||
@Override
|
@Override
|
||||||
public GpgSigningKey findSignatureVerificationKey(long keyId) {
|
public GpgSigningKey findSigningKey(long keyId) {
|
||||||
Long entityId = entityIds.get(keyId);
|
Long entityId = entityIds.get(keyId);
|
||||||
if (entityId != null) {
|
if (entityId != null) {
|
||||||
return new GpgSigningKey() {
|
return new GpgSigningKey() {
|
||||||
|
|||||||
@ -38,8 +38,8 @@ public class DefaultSshKeyManager extends BaseEntityManager<SshKey> implements S
|
|||||||
|
|
||||||
@Sessional
|
@Sessional
|
||||||
@Override
|
@Override
|
||||||
public SshKey findByDigest(String digest) {
|
public SshKey findByFingerprint(String fingerprint) {
|
||||||
SimpleExpression eq = Restrictions.eq("fingerprint", digest);
|
SimpleExpression eq = Restrictions.eq("fingerprint", fingerprint);
|
||||||
EntityCriteria<SshKey> entityCriteria = EntityCriteria.of(SshKey.class).add(eq);
|
EntityCriteria<SshKey> entityCriteria = EntityCriteria.of(SshKey.class).add(eq);
|
||||||
entityCriteria.setCacheable(true);
|
entityCriteria.setCacheable(true);
|
||||||
return find(entityCriteria);
|
return find(entityCriteria);
|
||||||
@ -66,7 +66,7 @@ public class DefaultSshKeyManager extends BaseEntityManager<SshKey> implements S
|
|||||||
diff.entriesOnlyOnLeft().values().forEach(sshKey -> delete(sshKey));
|
diff.entriesOnlyOnLeft().values().forEach(sshKey -> delete(sshKey));
|
||||||
|
|
||||||
diff.entriesOnlyOnRight().values().forEach(sshKey -> {
|
diff.entriesOnlyOnRight().values().forEach(sshKey -> {
|
||||||
if (findByDigest(sshKey.getFingerprint()) == null)
|
if (findByFingerprint(sshKey.getFingerprint()) == null)
|
||||||
create(sshKey);
|
create(sshKey);
|
||||||
else
|
else
|
||||||
logger.warn("SSH key is already in use (fingerprint: {})", sshKey.getFingerprint());
|
logger.warn("SSH key is already in use (fingerprint: {})", sshKey.getFingerprint());
|
||||||
|
|||||||
@ -61,9 +61,9 @@ public class DefaultSignatureVerificationManager implements SignatureVerificatio
|
|||||||
String emailAddress = committerIdent.getEmailAddress();
|
String emailAddress = committerIdent.getEmailAddress();
|
||||||
|
|
||||||
for (var signatureVerifier: signatureVerifiers) {
|
for (var signatureVerifier: signatureVerifiers) {
|
||||||
var result = signatureVerifier.verify(data, signatureData, emailAddress);
|
var signatureStartBytes = Constants.encodeASCII(signatureVerifier.getPrefix());
|
||||||
if (result != null)
|
if (RawParseUtils.match(signatureData, 0, signatureStartBytes) != -1)
|
||||||
return result;
|
return signatureVerifier.verify(data, signatureData, emailAddress);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -71,23 +71,19 @@ public class DefaultSignatureVerificationManager implements SignatureVerificatio
|
|||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public VerificationResult verifyTagSignature(byte[] rawTag) {
|
public VerificationResult verifyTagSignature(byte[] rawTag) {
|
||||||
byte[] signatureData = TagParser.getRawGpgSignature(rawTag);
|
|
||||||
if (signatureData == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The signature is just tacked onto the end of the message, which
|
|
||||||
// is last in the buffer.
|
|
||||||
byte[] data = Arrays.copyOfRange(rawTag, 0, rawTag.length - signatureData.length);
|
|
||||||
|
|
||||||
PersonIdent taggerIdent = TagParser.getTaggerIdent(rawTag);
|
PersonIdent taggerIdent = TagParser.getTaggerIdent(rawTag);
|
||||||
if (taggerIdent == null)
|
if (taggerIdent == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
for (var signatureVerifier: signatureVerifiers) {
|
for (var signatureVerifier: signatureVerifiers) {
|
||||||
var result = signatureVerifier.verify(data, signatureData, taggerIdent.getEmailAddress());
|
byte[] signatureStart = Constants.encodeASCII(signatureVerifier.getPrefix());
|
||||||
if (result != null)
|
byte[] signatureData = TagParser.getRawSignature(rawTag, signatureStart);
|
||||||
return result;
|
if (signatureData != null) {
|
||||||
|
// The signature is just tacked onto the end of the message, which
|
||||||
|
// is last in the buffer.
|
||||||
|
byte[] data = Arrays.copyOfRange(rawTag, 0, rawTag.length - signatureData.length);
|
||||||
|
return signatureVerifier.verify(data, signatureData, taggerIdent.getEmailAddress());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -97,9 +93,7 @@ public class DefaultSignatureVerificationManager implements SignatureVerificatio
|
|||||||
*/
|
*/
|
||||||
private static class TagParser {
|
private static class TagParser {
|
||||||
|
|
||||||
private static final byte[] hSignature = Constants.encodeASCII("-----BEGIN PGP SIGNATURE-----");
|
public static int nextStart(byte[] prefix, byte[] buffer, int from) {
|
||||||
|
|
||||||
private static int nextStart(byte[] prefix, byte[] buffer, int from) {
|
|
||||||
int stop = buffer.length - prefix.length + 1;
|
int stop = buffer.length - prefix.length + 1;
|
||||||
int ptr = from;
|
int ptr = from;
|
||||||
if (ptr > 0) {
|
if (ptr > 0) {
|
||||||
@ -124,19 +118,20 @@ public class DefaultSignatureVerificationManager implements SignatureVerificatio
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getSignatureStart(byte[] raw) {
|
public static int getSignatureStart(byte[] raw, byte[] signatureStart) {
|
||||||
int msgB = RawParseUtils.tagMessage(raw, 0);
|
int msgB = RawParseUtils.tagMessage(raw, 0);
|
||||||
if (msgB < 0) {
|
if (msgB < 0) {
|
||||||
return msgB;
|
return msgB;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the last signature start and return the rest
|
// Find the last signature start and return the rest
|
||||||
int start = nextStart(hSignature, raw, msgB);
|
int start = nextStart(signatureStart, raw, msgB);
|
||||||
if (start < 0) {
|
if (start < 0) {
|
||||||
return start;
|
return start;
|
||||||
}
|
}
|
||||||
int next = RawParseUtils.nextLF(raw, start);
|
int next = RawParseUtils.nextLF(raw, start);
|
||||||
while (next < raw.length) {
|
while (next < raw.length) {
|
||||||
int newStart = nextStart(hSignature, raw, next);
|
int newStart = nextStart(signatureStart, raw, next);
|
||||||
if (newStart < 0) {
|
if (newStart < 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -146,15 +141,15 @@ public class DefaultSignatureVerificationManager implements SignatureVerificatio
|
|||||||
return start;
|
return start;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] getRawGpgSignature(byte[] raw) {
|
public static byte[] getRawSignature(byte[] raw, byte[] signatureStart) {
|
||||||
int start = getSignatureStart(raw);
|
int start = getSignatureStart(raw, signatureStart);
|
||||||
if (start < 0) {
|
if (start < 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return Arrays.copyOfRange(raw, start, raw.length);
|
return Arrays.copyOfRange(raw, start, raw.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PersonIdent getTaggerIdent(byte[] raw) {
|
public static PersonIdent getTaggerIdent(byte[] raw) {
|
||||||
int nameB = RawParseUtils.tagger(raw, 0);
|
int nameB = RawParseUtils.tagger(raw, 0);
|
||||||
if (nameB < 0)
|
if (nameB < 0)
|
||||||
return null;
|
return null;
|
||||||
@ -162,5 +157,4 @@ public class DefaultSignatureVerificationManager implements SignatureVerificatio
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,11 @@
|
|||||||
package io.onedev.server.git.signatureverification;
|
package io.onedev.server.git.signatureverification;
|
||||||
|
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
|
||||||
import org.eclipse.jgit.revwalk.RevTag;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
public interface SignatureVerifier extends Serializable {
|
public interface SignatureVerifier extends Serializable {
|
||||||
|
|
||||||
@Nullable
|
|
||||||
VerificationResult verify(byte[] data, byte[] signatureData, String emailAddress);
|
VerificationResult verify(byte[] data, byte[] signatureData, String emailAddress);
|
||||||
|
|
||||||
|
String getPrefix();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,26 +10,20 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
|||||||
import org.bouncycastle.openpgp.*;
|
import org.bouncycastle.openpgp.*;
|
||||||
import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
|
import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
|
||||||
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
|
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
|
||||||
import org.eclipse.jgit.util.RawParseUtils;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class GpgSignatureVerifier implements SignatureVerifier {
|
public class GpgSignatureVerifier implements SignatureVerifier {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(GpgSignatureVerifier.class);
|
private static final Logger logger = LoggerFactory.getLogger(GpgSignatureVerifier.class);
|
||||||
|
|
||||||
private static final byte[] SIGNATURE_START = "-----BEGIN PGP SIGNATURE-----\n".getBytes(UTF_8);
|
|
||||||
|
|
||||||
private final GpgKeyManager gpgKeyManager;
|
private final GpgKeyManager gpgKeyManager;
|
||||||
|
|
||||||
private final SettingManager settingManager;
|
private final SettingManager settingManager;
|
||||||
@ -56,33 +50,31 @@ public class GpgSignatureVerifier implements SignatureVerifier {
|
|||||||
|
|
||||||
private GpgSigningKey loadKey(long keyId) {
|
private GpgSigningKey loadKey(long keyId) {
|
||||||
GpgSetting gpgSetting = settingManager.getGpgSetting();
|
GpgSetting gpgSetting = settingManager.getGpgSetting();
|
||||||
GpgSigningKey verificationKey = gpgSetting.findSignatureVerificationKey(keyId);
|
GpgSigningKey verificationKey = gpgSetting.findSigningKey(keyId);
|
||||||
if (verificationKey == null)
|
if (verificationKey == null)
|
||||||
verificationKey = gpgKeyManager.findSignatureVerificationKey(keyId);
|
verificationKey = gpgKeyManager.findSigningKey(keyId);
|
||||||
return verificationKey;
|
return verificationKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
@Override
|
||||||
public VerificationResult verify(byte[] data, byte[] signatureData, String emailAddress) {
|
public VerificationResult verify(byte[] data, byte[] signatureData, String emailAddress) {
|
||||||
if (RawParseUtils.match(signatureData, 0, SIGNATURE_START) != -1) {
|
|
||||||
try (InputStream is = new ByteArrayInputStream(signatureData)) {
|
try (InputStream is = new ByteArrayInputStream(signatureData)) {
|
||||||
PGPSignature signature = parseSignature(is);
|
PGPSignature signature = parseSignature(is);
|
||||||
if (signature != null) {
|
if (signature != null) {
|
||||||
GpgSigningKey verificationKey = loadKey(signature.getKeyID());
|
GpgSigningKey signingKey = loadKey(signature.getKeyID());
|
||||||
if (verificationKey != null) {
|
if (signingKey != null) {
|
||||||
if (verificationKey.getEmailAddresses() != null
|
if (signingKey.getEmailAddresses() != null
|
||||||
&& !verificationKey.getEmailAddresses().contains(emailAddress)) {
|
&& !signingKey.getEmailAddresses().contains(emailAddress)) {
|
||||||
return new GpgVerificationFailed(verificationKey, "Not a verified email of signing GPG key");
|
return new GpgVerificationFailed(signingKey, "Not a verified email of signing GPG key");
|
||||||
}
|
}
|
||||||
signature.init(
|
signature.init(
|
||||||
new JcaPGPContentVerifierBuilderProvider().setProvider(BouncyCastleProvider.PROVIDER_NAME),
|
new JcaPGPContentVerifierBuilderProvider().setProvider(BouncyCastleProvider.PROVIDER_NAME),
|
||||||
verificationKey.getPublicKey());
|
signingKey.getPublicKey());
|
||||||
signature.update(data);
|
signature.update(data);
|
||||||
if (signature.verify())
|
if (signature.verify())
|
||||||
return new GpgVerificationSuccessful(verificationKey);
|
return new GpgVerificationSuccessful(signingKey);
|
||||||
else
|
else
|
||||||
return new GpgVerificationFailed(verificationKey, "Invalid GPG signature");
|
return new GpgVerificationFailed(signingKey, "Invalid GPG signature");
|
||||||
} else {
|
} else {
|
||||||
return new GpgVerificationFailed(null, "Signed with an unknown GPG key "
|
return new GpgVerificationFailed(null, "Signed with an unknown GPG key "
|
||||||
+ "(key ID: " + GpgUtils.getKeyIDString(signature.getKeyID()) + ")");
|
+ "(key ID: " + GpgUtils.getKeyIDString(signature.getKeyID()) + ")");
|
||||||
@ -96,9 +88,11 @@ public class GpgSignatureVerifier implements SignatureVerifier {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPrefix() {
|
||||||
|
return "-----BEGIN PGP SIGNATURE-----";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,22 +14,17 @@ import org.bouncycastle.crypto.digests.SHA512Digest;
|
|||||||
import org.bouncycastle.crypto.signers.Ed25519Signer;
|
import org.bouncycastle.crypto.signers.Ed25519Signer;
|
||||||
import org.bouncycastle.crypto.signers.RSADigestSigner;
|
import org.bouncycastle.crypto.signers.RSADigestSigner;
|
||||||
import org.bouncycastle.util.io.pem.PemReader;
|
import org.bouncycastle.util.io.pem.PemReader;
|
||||||
import org.eclipse.jgit.util.RawParseUtils;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
|
||||||
import static org.apache.sshd.common.digest.DigestUtils.getFingerPrint;
|
import static org.apache.sshd.common.digest.DigestUtils.getFingerPrint;
|
||||||
import static org.bouncycastle.crypto.util.OpenSSHPublicKeyUtil.parsePublicKey;
|
import static org.bouncycastle.crypto.util.OpenSSHPublicKeyUtil.parsePublicKey;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class SshSignatureVerifier implements SignatureVerifier {
|
public class SshSignatureVerifier implements SignatureVerifier {
|
||||||
|
|
||||||
private static final byte[] SIGNATURE_START = "-----BEGIN SSH SIGNATURE-----\n".getBytes(UTF_8);
|
|
||||||
|
|
||||||
private final SshKeyManager sshKeyManager;
|
private final SshKeyManager sshKeyManager;
|
||||||
|
|
||||||
private final EmailAddressManager emailAddressManager;
|
private final EmailAddressManager emailAddressManager;
|
||||||
@ -40,10 +35,8 @@ public class SshSignatureVerifier implements SignatureVerifier {
|
|||||||
this.emailAddressManager = emailAddressManager;
|
this.emailAddressManager = emailAddressManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
@Override
|
||||||
public VerificationResult verify(byte[] data, byte[] signatureData, String emailAddressValue) {
|
public VerificationResult verify(byte[] data, byte[] signatureData, String emailAddressValue) {
|
||||||
if (RawParseUtils.match(signatureData, 0, SIGNATURE_START) != -1) {
|
|
||||||
try (var pemReader = new PemReader(new StringReader(new String(signatureData)))) {
|
try (var pemReader = new PemReader(new StringReader(new String(signatureData)))) {
|
||||||
var signatureBlob = pemReader.readPemObject().getContent();
|
var signatureBlob = pemReader.readPemObject().getContent();
|
||||||
var signatureBlobReader = new TypesReader(signatureBlob);
|
var signatureBlobReader = new TypesReader(signatureBlob);
|
||||||
@ -59,7 +52,7 @@ public class SshSignatureVerifier implements SignatureVerifier {
|
|||||||
var publicKey = parsePublicKey(publicKeyBytes);
|
var publicKey = parsePublicKey(publicKeyBytes);
|
||||||
var keyInfo = new SshKeyInfo(keyType, fingerprint);
|
var keyInfo = new SshKeyInfo(keyType, fingerprint);
|
||||||
|
|
||||||
var sshKey = sshKeyManager.findByDigest(fingerprint);
|
var sshKey = sshKeyManager.findByFingerprint(fingerprint);
|
||||||
if (sshKey == null)
|
if (sshKey == null)
|
||||||
return new SshVerificationFailed(keyInfo, "Signed with an unknown ssh key");
|
return new SshVerificationFailed(keyInfo, "Signed with an unknown ssh key");
|
||||||
|
|
||||||
@ -120,9 +113,11 @@ public class SshSignatureVerifier implements SignatureVerifier {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw ExceptionUtils.unchecked(e);
|
throw ExceptionUtils.unchecked(e);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPrefix() {
|
||||||
|
return "-----BEGIN SSH SIGNATURE-----";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -117,7 +117,7 @@ public class GpgSetting implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public GpgSigningKey findSignatureVerificationKey(long keyId) {
|
public GpgSigningKey findSigningKey(long keyId) {
|
||||||
if (getSigningKey() != null && getSigningKey().getPublicKey().getKeyID() == keyId) {
|
if (getSigningKey() != null && getSigningKey().getPublicKey().getKeyID() == keyId) {
|
||||||
return new GpgSigningKey() {
|
return new GpgSigningKey() {
|
||||||
|
|
||||||
|
|||||||
@ -47,7 +47,7 @@ public class DefaultSshAuthenticator implements SshAuthenticator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String digest = KeyUtils.getFingerPrint(BuiltinDigests.sha256, key);
|
String digest = KeyUtils.getFingerPrint(BuiltinDigests.sha256, key);
|
||||||
SshKey sshKey = sshKeyManager.findByDigest(digest);
|
SshKey sshKey = sshKeyManager.findByFingerprint(digest);
|
||||||
if (sshKey != null) {
|
if (sshKey != null) {
|
||||||
session.setAttribute(ATTR_PUBLIC_KEY_OWNER_ID, sshKey.getOwner().getId());
|
session.setAttribute(ATTR_PUBLIC_KEY_OWNER_ID, sshKey.getOwner().getId());
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import io.onedev.server.util.ProjectScopedCommit;
|
|||||||
import io.onedev.server.web.component.commit.message.CommitMessagePanel;
|
import io.onedev.server.web.component.commit.message.CommitMessagePanel;
|
||||||
import io.onedev.server.web.component.commit.status.CommitStatusLink;
|
import io.onedev.server.web.component.commit.status.CommitStatusLink;
|
||||||
import io.onedev.server.web.component.contributorpanel.ContributorPanel;
|
import io.onedev.server.web.component.contributorpanel.ContributorPanel;
|
||||||
import io.onedev.server.web.component.gitsignature.VerificationResultPanel;
|
import io.onedev.server.web.component.gitsignature.SignatureStatusPanel;
|
||||||
import io.onedev.server.web.component.link.ViewStateAwarePageLink;
|
import io.onedev.server.web.component.link.ViewStateAwarePageLink;
|
||||||
import io.onedev.server.web.component.link.copytoclipboard.CopyToClipboardLink;
|
import io.onedev.server.web.component.link.copytoclipboard.CopyToClipboardLink;
|
||||||
import io.onedev.server.web.component.user.contributoravatars.ContributorAvatars;
|
import io.onedev.server.web.component.user.contributoravatars.ContributorAvatars;
|
||||||
@ -80,7 +80,7 @@ public abstract class CommitInfoPanel extends GenericPanel<ProjectScopedCommit>
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
add(new VerificationResultPanel("signature") {
|
add(new SignatureStatusPanel("signature") {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RevObject getRevObject() {
|
protected RevObject getRevObject() {
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import io.onedev.server.web.component.commit.message.CommitMessagePanel;
|
|||||||
import io.onedev.server.web.component.commit.status.CommitStatusLink;
|
import io.onedev.server.web.component.commit.status.CommitStatusLink;
|
||||||
import io.onedev.server.web.component.commit.status.CommitStatusSupport;
|
import io.onedev.server.web.component.commit.status.CommitStatusSupport;
|
||||||
import io.onedev.server.web.component.contributorpanel.ContributorPanel;
|
import io.onedev.server.web.component.contributorpanel.ContributorPanel;
|
||||||
import io.onedev.server.web.component.gitsignature.VerificationResultPanel;
|
import io.onedev.server.web.component.gitsignature.SignatureStatusPanel;
|
||||||
import io.onedev.server.web.component.link.ViewStateAwarePageLink;
|
import io.onedev.server.web.component.link.ViewStateAwarePageLink;
|
||||||
import io.onedev.server.web.component.link.copytoclipboard.CopyToClipboardLink;
|
import io.onedev.server.web.component.link.copytoclipboard.CopyToClipboardLink;
|
||||||
import io.onedev.server.web.component.savedquery.SavedQueriesClosed;
|
import io.onedev.server.web.component.savedquery.SavedQueriesClosed;
|
||||||
@ -641,7 +641,7 @@ public abstract class CommitListPanel extends Panel {
|
|||||||
|
|
||||||
getCommitIdsToQueryStatus().add(commit.copy());
|
getCommitIdsToQueryStatus().add(commit.copy());
|
||||||
|
|
||||||
item.add(new VerificationResultPanel("signature") {
|
item.add(new SignatureStatusPanel("signature") {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RevObject getRevObject() {
|
protected RevObject getRevObject() {
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import org.apache.wicket.model.LoadableDetachableModel;
|
|||||||
import org.eclipse.jgit.revwalk.RevObject;
|
import org.eclipse.jgit.revwalk.RevObject;
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
public abstract class VerificationResultPanel extends DropdownLink {
|
public abstract class SignatureStatusPanel extends DropdownLink {
|
||||||
|
|
||||||
private IModel<VerificationResult> model = new LoadableDetachableModel<>() {
|
private IModel<VerificationResult> model = new LoadableDetachableModel<>() {
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ public abstract class VerificationResultPanel extends DropdownLink {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public VerificationResultPanel(String id) {
|
public SignatureStatusPanel(String id) {
|
||||||
super(id);
|
super(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ public abstract class InsertGpgKeyPanel extends Panel {
|
|||||||
gpgKey.setOwner(getUser());
|
gpgKey.setOwner(getUser());
|
||||||
gpgKey.setKeyId(gpgKey.getKeyIds().get(0));
|
gpgKey.setKeyId(gpgKey.getKeyIds().get(0));
|
||||||
|
|
||||||
if (gpgKey.getKeyIds().stream().anyMatch(it->gpgKeyManager.findSignatureVerificationKey(it)!=null)) {
|
if (gpgKey.getKeyIds().stream().anyMatch(it->gpgKeyManager.findSigningKey(it)!=null)) {
|
||||||
editor.error(new Path(new PathNode.Named("content")), "This key or one of its subkey is already in use");
|
editor.error(new Path(new PathNode.Named("content")), "This key or one of its subkey is already in use");
|
||||||
target.add(form);
|
target.add(form);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -54,7 +54,7 @@ public abstract class InsertSshKeyPanel extends Panel {
|
|||||||
SshKey sshKey = (SshKey) editor.getModelObject();
|
SshKey sshKey = (SshKey) editor.getModelObject();
|
||||||
sshKey.fingerprint();
|
sshKey.fingerprint();
|
||||||
|
|
||||||
if (sshKeyManager.findByDigest(sshKey.getFingerprint()) != null) {
|
if (sshKeyManager.findByFingerprint(sshKey.getFingerprint()) != null) {
|
||||||
editor.error(new Path(new PathNode.Named("content")), "This key is already in use");
|
editor.error(new Path(new PathNode.Named("content")), "This key is already in use");
|
||||||
target.add(form);
|
target.add(form);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -32,7 +32,7 @@ import io.onedev.server.web.component.branch.create.CreateBranchLink;
|
|||||||
import io.onedev.server.web.component.contributorpanel.ContributorPanel;
|
import io.onedev.server.web.component.contributorpanel.ContributorPanel;
|
||||||
import io.onedev.server.web.component.createtag.CreateTagLink;
|
import io.onedev.server.web.component.createtag.CreateTagLink;
|
||||||
import io.onedev.server.web.component.diff.revision.RevisionDiffPanel;
|
import io.onedev.server.web.component.diff.revision.RevisionDiffPanel;
|
||||||
import io.onedev.server.web.component.gitsignature.VerificationResultPanel;
|
import io.onedev.server.web.component.gitsignature.SignatureStatusPanel;
|
||||||
import io.onedev.server.web.component.job.jobinfo.JobInfoButton;
|
import io.onedev.server.web.component.job.jobinfo.JobInfoButton;
|
||||||
import io.onedev.server.web.component.link.ViewStateAwarePageLink;
|
import io.onedev.server.web.component.link.ViewStateAwarePageLink;
|
||||||
import io.onedev.server.web.component.link.copytoclipboard.CopyToClipboardLink;
|
import io.onedev.server.web.component.link.copytoclipboard.CopyToClipboardLink;
|
||||||
@ -277,7 +277,7 @@ public class CommitDetailPage extends ProjectPage implements RevisionDiff.Annota
|
|||||||
add(new ContributorAvatars("contributorAvatars", getCommit().getAuthorIdent(), getCommit().getCommitterIdent()));
|
add(new ContributorAvatars("contributorAvatars", getCommit().getAuthorIdent(), getCommit().getCommitterIdent()));
|
||||||
add(new ContributorPanel("contribution", getCommit().getAuthorIdent(), getCommit().getCommitterIdent()));
|
add(new ContributorPanel("contribution", getCommit().getAuthorIdent(), getCommit().getCommitterIdent()));
|
||||||
|
|
||||||
add(new VerificationResultPanel("signature") {
|
add(new SignatureStatusPanel("signature") {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RevObject getRevObject() {
|
protected RevObject getRevObject() {
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import io.onedev.server.util.DateUtils;
|
|||||||
import io.onedev.server.web.WebConstants;
|
import io.onedev.server.web.WebConstants;
|
||||||
import io.onedev.server.web.behavior.WebSocketObserver;
|
import io.onedev.server.web.behavior.WebSocketObserver;
|
||||||
import io.onedev.server.web.component.commit.message.CommitMessagePanel;
|
import io.onedev.server.web.component.commit.message.CommitMessagePanel;
|
||||||
import io.onedev.server.web.component.gitsignature.VerificationResultPanel;
|
import io.onedev.server.web.component.gitsignature.SignatureStatusPanel;
|
||||||
import io.onedev.server.web.component.link.ViewStateAwarePageLink;
|
import io.onedev.server.web.component.link.ViewStateAwarePageLink;
|
||||||
import io.onedev.server.web.component.link.copytoclipboard.CopyToClipboardLink;
|
import io.onedev.server.web.component.link.copytoclipboard.CopyToClipboardLink;
|
||||||
import io.onedev.server.web.component.user.ident.Mode;
|
import io.onedev.server.web.component.user.ident.Mode;
|
||||||
@ -90,7 +90,7 @@ class PullRequestUpdatedPanel extends GenericPanel<PullRequestUpdate> {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
item.add(new VerificationResultPanel("signature") {
|
item.add(new SignatureStatusPanel("signature") {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RevObject getRevObject() {
|
protected RevObject getRevObject() {
|
||||||
|
|||||||
@ -69,7 +69,7 @@ import io.onedev.server.web.behavior.OnTypingDoneBehavior;
|
|||||||
import io.onedev.server.web.component.commit.status.CommitStatusLink;
|
import io.onedev.server.web.component.commit.status.CommitStatusLink;
|
||||||
import io.onedev.server.web.component.contributorpanel.ContributorPanel;
|
import io.onedev.server.web.component.contributorpanel.ContributorPanel;
|
||||||
import io.onedev.server.web.component.datatable.DefaultDataTable;
|
import io.onedev.server.web.component.datatable.DefaultDataTable;
|
||||||
import io.onedev.server.web.component.gitsignature.VerificationResultPanel;
|
import io.onedev.server.web.component.gitsignature.SignatureStatusPanel;
|
||||||
import io.onedev.server.web.component.link.ArchiveMenuLink;
|
import io.onedev.server.web.component.link.ArchiveMenuLink;
|
||||||
import io.onedev.server.web.component.link.ViewStateAwarePageLink;
|
import io.onedev.server.web.component.link.ViewStateAwarePageLink;
|
||||||
import io.onedev.server.web.component.modal.ModalLink;
|
import io.onedev.server.web.component.modal.ModalLink;
|
||||||
@ -338,7 +338,7 @@ public class ProjectTagsPage extends ProjectPage {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (ref.getObj() instanceof RevTag) {
|
if (ref.getObj() instanceof RevTag) {
|
||||||
fragment.add(new VerificationResultPanel("signature") {
|
fragment.add(new SignatureStatusPanel("signature") {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RevObject getRevObject() {
|
protected RevObject getRevObject() {
|
||||||
|
|||||||
@ -39,7 +39,7 @@ import org.eclipse.jgit.util.StringUtils;
|
|||||||
public class RevTag extends RevObject {
|
public class RevTag extends RevObject {
|
||||||
|
|
||||||
private static final byte[] hSignature = Constants
|
private static final byte[] hSignature = Constants
|
||||||
.encodeASCII("-----BEGIN PGP SIGNATURE-----"); //$NON-NLS-1$
|
.encodeASCII("-----BEGIN "); //$NON-NLS-1$
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse an annotated tag from its canonical format.
|
* Parse an annotated tag from its canonical format.
|
||||||
|
|||||||
@ -0,0 +1,127 @@
|
|||||||
|
package io.onedev.server.git.signatureverification.ssh;
|
||||||
|
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import com.google.common.io.Resources;
|
||||||
|
import io.onedev.commons.utils.FileUtils;
|
||||||
|
import io.onedev.server.entitymanager.EmailAddressManager;
|
||||||
|
import io.onedev.server.entitymanager.GpgKeyManager;
|
||||||
|
import io.onedev.server.entitymanager.SettingManager;
|
||||||
|
import io.onedev.server.entitymanager.SshKeyManager;
|
||||||
|
import io.onedev.server.git.signatureverification.DefaultSignatureVerificationManager;
|
||||||
|
import io.onedev.server.git.signatureverification.VerificationSuccessful;
|
||||||
|
import io.onedev.server.git.signatureverification.gpg.GpgSignatureVerifier;
|
||||||
|
import io.onedev.server.git.signatureverification.gpg.GpgSigningKey;
|
||||||
|
import io.onedev.server.model.EmailAddress;
|
||||||
|
import io.onedev.server.model.GpgKey;
|
||||||
|
import io.onedev.server.model.SshKey;
|
||||||
|
import io.onedev.server.model.User;
|
||||||
|
import io.onedev.server.model.support.administration.GpgSetting;
|
||||||
|
import io.onedev.server.util.GpgUtils;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
|
import org.eclipse.jgit.api.Git;
|
||||||
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.revwalk.RevObject;
|
||||||
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyLong;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class SignatureVerifierTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void verify() {
|
||||||
|
var tempDir = FileUtils.createTempDir();
|
||||||
|
try (InputStream is = Resources.getResource(SignatureVerifierTest.class, "git-signature.zip").openStream()) {
|
||||||
|
FileUtils.unzip(is, tempDir);
|
||||||
|
try (Git git = Git.open(tempDir)) {
|
||||||
|
var emailAddressValue = "foo@example.com";
|
||||||
|
var owner = new User();
|
||||||
|
owner.setId(1L);
|
||||||
|
var emailAddress = new EmailAddress();
|
||||||
|
emailAddress.setValue(emailAddressValue);
|
||||||
|
emailAddress.setOwner(owner);
|
||||||
|
emailAddress.setVerificationCode(null);
|
||||||
|
|
||||||
|
var emailAddressManager = mock(EmailAddressManager.class);
|
||||||
|
when(emailAddressManager.findByValue(any())).thenReturn(emailAddress);
|
||||||
|
|
||||||
|
var sshKeyManager = mock(SshKeyManager.class);
|
||||||
|
var sshKey = new SshKey();
|
||||||
|
sshKey.setOwner(owner);
|
||||||
|
when(sshKeyManager.findByFingerprint(any())).thenReturn(sshKey);
|
||||||
|
|
||||||
|
var gpgKeyManager = mock(GpgKeyManager.class);
|
||||||
|
when(gpgKeyManager.findSigningKey(anyLong())).thenReturn(new GpgSigningKey() {
|
||||||
|
@Override
|
||||||
|
public PGPPublicKey getPublicKey() {
|
||||||
|
var armoredKey = "" +
|
||||||
|
"-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
|
||||||
|
"\n" +
|
||||||
|
"mDMEZIUyvxYJKwYBBAHaRw8BAQdA72vvNIlCWWDvN4a/RRtg7+GkxZ2DFNry7zpn\n" +
|
||||||
|
"Xbj/vAe0FUZvbyA8Zm9vQGV4YW1wbGUuY29tPoiZBBMWCgBBFiEE0KAUvnJD8mPD\n" +
|
||||||
|
"ePn8LNDZisNCH84FAmSFMr8CGwMFCQPCZwAFCwkIBwICIgIGFQoJCAsCBBYCAwEC\n" +
|
||||||
|
"HgcCF4AACgkQLNDZisNCH84nXQD+L35Lun8E68mJWfPReURT8RaMIO5n0/CIbj2q\n" +
|
||||||
|
"9Mz3mN4BAOd0ygKOuCAVw3jFQGlBRchHHzLW86cifCDhskS4mwEDuDgEZIUyvxIK\n" +
|
||||||
|
"KwYBBAGXVQEFAQEHQGCDpBqVzCSAgi6wQkbXsVWobl0DRkeIoFobHbbttkERAwEI\n" +
|
||||||
|
"B4h+BBgWCgAmFiEE0KAUvnJD8mPDePn8LNDZisNCH84FAmSFMr8CGwwFCQPCZwAA\n" +
|
||||||
|
"CgkQLNDZisNCH87VQAD/aRrNMjjW2wq52B2Ed3IBdbJNqLur9hNDZotnebjFwI4A\n" +
|
||||||
|
"/i0iZZTUO3X3n45RtntAknKA0MBLcmYsn2rOzshQMcUO\n" +
|
||||||
|
"=m9mT\n" +
|
||||||
|
"-----END PGP PUBLIC KEY BLOCK-----";
|
||||||
|
return GpgUtils.parse(armoredKey).iterator().next();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> getEmailAddresses() {
|
||||||
|
return Sets.newHashSet(emailAddressValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var gpgSetting = mock(GpgSetting.class);
|
||||||
|
when(gpgSetting.findSigningKey(anyLong())).thenReturn(null);
|
||||||
|
|
||||||
|
var settingManager = mock(SettingManager.class);
|
||||||
|
when(settingManager.getGpgSetting()).thenReturn(gpgSetting);
|
||||||
|
|
||||||
|
var signatureVerifiers = Sets.newHashSet(
|
||||||
|
new SshSignatureVerifier(sshKeyManager, emailAddressManager),
|
||||||
|
new GpgSignatureVerifier(gpgKeyManager, settingManager)
|
||||||
|
);
|
||||||
|
var commitSignatureManager = new DefaultSignatureVerificationManager(signatureVerifiers);
|
||||||
|
|
||||||
|
RevObject revObject = git.getRepository().parseCommit(ObjectId.fromString("2c968a0c073b0d1887aae917abea3a56629b3e0a"));
|
||||||
|
assert(commitSignatureManager.verifySignature(revObject) instanceof VerificationSuccessful);
|
||||||
|
|
||||||
|
revObject = git.getRepository().parseCommit(ObjectId.fromString("adb9fa57a4a139d2326075424322a866a7c7b115"));
|
||||||
|
assert(commitSignatureManager.verifySignature(revObject) instanceof VerificationSuccessful);
|
||||||
|
|
||||||
|
revObject = git.getRepository().parseCommit(ObjectId.fromString("cd0af2811fc837f68d001b06cbce1e1101f73942"));
|
||||||
|
assert(commitSignatureManager.verifySignature(revObject) instanceof VerificationSuccessful);
|
||||||
|
|
||||||
|
try (var revWalk = new RevWalk(git.getRepository())) {
|
||||||
|
revObject = revWalk.parseTag(git.getRepository().resolve("v1"));
|
||||||
|
assert(commitSignatureManager.verifySignature(revObject) instanceof VerificationSuccessful);
|
||||||
|
|
||||||
|
revObject = revWalk.parseTag(git.getRepository().resolve("v2"));
|
||||||
|
assert(commitSignatureManager.verifySignature(revObject) instanceof VerificationSuccessful);
|
||||||
|
|
||||||
|
revObject = revWalk.parseTag(git.getRepository().resolve("v3"));
|
||||||
|
assert(commitSignatureManager.verifySignature(revObject) instanceof VerificationSuccessful);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} finally {
|
||||||
|
FileUtils.deleteDir(tempDir, 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user