diff --git a/server-core/src/main/java/io/onedev/server/entitymanager/GpgKeyManager.java b/server-core/src/main/java/io/onedev/server/entitymanager/GpgKeyManager.java index 73ab0d4f4c..90a655adc6 100644 --- a/server-core/src/main/java/io/onedev/server/entitymanager/GpgKeyManager.java +++ b/server-core/src/main/java/io/onedev/server/entitymanager/GpgKeyManager.java @@ -9,7 +9,7 @@ import io.onedev.server.persistence.dao.EntityManager; public interface GpgKeyManager extends EntityManager { @Nullable - GpgSigningKey findSignatureVerificationKey(long keyId); + GpgSigningKey findSigningKey(long keyId); void create(GpgKey gpgKey); } diff --git a/server-core/src/main/java/io/onedev/server/entitymanager/SshKeyManager.java b/server-core/src/main/java/io/onedev/server/entitymanager/SshKeyManager.java index b6b29dd875..522a88d7c5 100644 --- a/server-core/src/main/java/io/onedev/server/entitymanager/SshKeyManager.java +++ b/server-core/src/main/java/io/onedev/server/entitymanager/SshKeyManager.java @@ -11,7 +11,7 @@ import io.onedev.server.persistence.dao.EntityManager; public interface SshKeyManager extends EntityManager { @Nullable - SshKey findByDigest(String digest); + SshKey findByFingerprint(String fingerprint); void syncSshKeys(User user, Collection sshKeys); diff --git a/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultGpgKeyManager.java b/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultGpgKeyManager.java index 7060233074..05b4792954 100644 --- a/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultGpgKeyManager.java +++ b/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultGpgKeyManager.java @@ -96,7 +96,7 @@ public class DefaultGpgKeyManager extends BaseEntityManager implements G @Sessional @Override - public GpgSigningKey findSignatureVerificationKey(long keyId) { + public GpgSigningKey findSigningKey(long keyId) { Long entityId = entityIds.get(keyId); if (entityId != null) { return new GpgSigningKey() { diff --git a/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultSshKeyManager.java b/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultSshKeyManager.java index e8ccfe0231..72b9161148 100644 --- a/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultSshKeyManager.java +++ b/server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultSshKeyManager.java @@ -38,8 +38,8 @@ public class DefaultSshKeyManager extends BaseEntityManager implements S @Sessional @Override - public SshKey findByDigest(String digest) { - SimpleExpression eq = Restrictions.eq("fingerprint", digest); + public SshKey findByFingerprint(String fingerprint) { + SimpleExpression eq = Restrictions.eq("fingerprint", fingerprint); EntityCriteria entityCriteria = EntityCriteria.of(SshKey.class).add(eq); entityCriteria.setCacheable(true); return find(entityCriteria); @@ -66,7 +66,7 @@ public class DefaultSshKeyManager extends BaseEntityManager implements S diff.entriesOnlyOnLeft().values().forEach(sshKey -> delete(sshKey)); diff.entriesOnlyOnRight().values().forEach(sshKey -> { - if (findByDigest(sshKey.getFingerprint()) == null) + if (findByFingerprint(sshKey.getFingerprint()) == null) create(sshKey); else logger.warn("SSH key is already in use (fingerprint: {})", sshKey.getFingerprint()); diff --git a/server-core/src/main/java/io/onedev/server/git/GitUtils.java b/server-core/src/main/java/io/onedev/server/git/GitUtils.java index 5fc859cbe6..85c85e9405 100644 --- a/server-core/src/main/java/io/onedev/server/git/GitUtils.java +++ b/server-core/src/main/java/io/onedev/server/git/GitUtils.java @@ -553,7 +553,7 @@ public class GitUtils { blobName = StringUtils.substringAfterLast(blobPath, "/"); return blobName; } - + public static void sign(ObjectBuilder object, PGPSecretKeyRing signingKey) { JcePBESecretKeyDecryptorBuilder decryptorBuilder = new JcePBESecretKeyDecryptorBuilder() .setProvider(BouncyCastleProvider.PROVIDER_NAME); diff --git a/server-core/src/main/java/io/onedev/server/git/signatureverification/DefaultSignatureVerificationManager.java b/server-core/src/main/java/io/onedev/server/git/signatureverification/DefaultSignatureVerificationManager.java index 8f557c10d8..a7bde23b5a 100644 --- a/server-core/src/main/java/io/onedev/server/git/signatureverification/DefaultSignatureVerificationManager.java +++ b/server-core/src/main/java/io/onedev/server/git/signatureverification/DefaultSignatureVerificationManager.java @@ -61,9 +61,9 @@ public class DefaultSignatureVerificationManager implements SignatureVerificatio String emailAddress = committerIdent.getEmailAddress(); for (var signatureVerifier: signatureVerifiers) { - var result = signatureVerifier.verify(data, signatureData, emailAddress); - if (result != null) - return result; + var signatureStartBytes = Constants.encodeASCII(signatureVerifier.getPrefix()); + if (RawParseUtils.match(signatureData, 0, signatureStartBytes) != -1) + return signatureVerifier.verify(data, signatureData, emailAddress); } return null; } @@ -71,24 +71,20 @@ public class DefaultSignatureVerificationManager implements SignatureVerificatio @Nullable @Override 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); if (taggerIdent == null) return null; for (var signatureVerifier: signatureVerifiers) { - var result = signatureVerifier.verify(data, signatureData, taggerIdent.getEmailAddress()); - if (result != null) - return result; - } + byte[] signatureStart = Constants.encodeASCII(signatureVerifier.getPrefix()); + byte[] signatureData = TagParser.getRawSignature(rawTag, signatureStart); + 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; } @@ -96,10 +92,8 @@ public class DefaultSignatureVerificationManager implements SignatureVerificatio * Copied from JGit */ private static class TagParser { - - private static final byte[] hSignature = Constants.encodeASCII("-----BEGIN PGP SIGNATURE-----"); - - private static int nextStart(byte[] prefix, byte[] buffer, int from) { + + public static int nextStart(byte[] prefix, byte[] buffer, int from) { int stop = buffer.length - prefix.length + 1; int ptr = from; if (ptr > 0) { @@ -123,20 +117,21 @@ public class DefaultSignatureVerificationManager implements SignatureVerificatio } return -1; } - - private static int getSignatureStart(byte[] raw) { + + public static int getSignatureStart(byte[] raw, byte[] signatureStart) { int msgB = RawParseUtils.tagMessage(raw, 0); if (msgB < 0) { return msgB; } + // Find the last signature start and return the rest - int start = nextStart(hSignature, raw, msgB); + int start = nextStart(signatureStart, raw, msgB); if (start < 0) { return start; } int next = RawParseUtils.nextLF(raw, start); while (next < raw.length) { - int newStart = nextStart(hSignature, raw, next); + int newStart = nextStart(signatureStart, raw, next); if (newStart < 0) { break; } @@ -145,22 +140,21 @@ public class DefaultSignatureVerificationManager implements SignatureVerificatio } return start; } - - private static byte[] getRawGpgSignature(byte[] raw) { - int start = getSignatureStart(raw); + + public static byte[] getRawSignature(byte[] raw, byte[] signatureStart) { + int start = getSignatureStart(raw, signatureStart); if (start < 0) { return null; } 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); if (nameB < 0) return null; return RawParseUtils.parsePersonIdent(raw, nameB); } - + } - } diff --git a/server-core/src/main/java/io/onedev/server/git/signatureverification/SignatureVerifier.java b/server-core/src/main/java/io/onedev/server/git/signatureverification/SignatureVerifier.java index 6d19960d9d..6f7a1e26f2 100644 --- a/server-core/src/main/java/io/onedev/server/git/signatureverification/SignatureVerifier.java +++ b/server-core/src/main/java/io/onedev/server/git/signatureverification/SignatureVerifier.java @@ -1,14 +1,11 @@ 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; public interface SignatureVerifier extends Serializable { - @Nullable VerificationResult verify(byte[] data, byte[] signatureData, String emailAddress); + String getPrefix(); + } diff --git a/server-core/src/main/java/io/onedev/server/git/signatureverification/gpg/GpgSignatureVerifier.java b/server-core/src/main/java/io/onedev/server/git/signatureverification/gpg/GpgSignatureVerifier.java index 3dbf2b7a53..4417fc968a 100644 --- a/server-core/src/main/java/io/onedev/server/git/signatureverification/gpg/GpgSignatureVerifier.java +++ b/server-core/src/main/java/io/onedev/server/git/signatureverification/gpg/GpgSignatureVerifier.java @@ -10,26 +10,20 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.*; import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; -import org.eclipse.jgit.util.RawParseUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Singleton; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import static java.nio.charset.StandardCharsets.UTF_8; - @Singleton public class GpgSignatureVerifier implements SignatureVerifier { 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 SettingManager settingManager; @@ -56,49 +50,49 @@ public class GpgSignatureVerifier implements SignatureVerifier { private GpgSigningKey loadKey(long keyId) { GpgSetting gpgSetting = settingManager.getGpgSetting(); - GpgSigningKey verificationKey = gpgSetting.findSignatureVerificationKey(keyId); + GpgSigningKey verificationKey = gpgSetting.findSigningKey(keyId); if (verificationKey == null) - verificationKey = gpgKeyManager.findSignatureVerificationKey(keyId); + verificationKey = gpgKeyManager.findSigningKey(keyId); return verificationKey; } - @Nullable @Override public VerificationResult verify(byte[] data, byte[] signatureData, String emailAddress) { - if (RawParseUtils.match(signatureData, 0, SIGNATURE_START) != -1) { - try (InputStream is = new ByteArrayInputStream(signatureData)) { - PGPSignature signature = parseSignature(is); - if (signature != null) { - GpgSigningKey verificationKey = loadKey(signature.getKeyID()); - if (verificationKey != null) { - if (verificationKey.getEmailAddresses() != null - && !verificationKey.getEmailAddresses().contains(emailAddress)) { - return new GpgVerificationFailed(verificationKey, "Not a verified email of signing GPG key"); - } - signature.init( - new JcaPGPContentVerifierBuilderProvider().setProvider(BouncyCastleProvider.PROVIDER_NAME), - verificationKey.getPublicKey()); - signature.update(data); - if (signature.verify()) - return new GpgVerificationSuccessful(verificationKey); - else - return new GpgVerificationFailed(verificationKey, "Invalid GPG signature"); - } else { - return new GpgVerificationFailed(null, "Signed with an unknown GPG key " - + "(key ID: " + GpgUtils.getKeyIDString(signature.getKeyID()) + ")"); + try (InputStream is = new ByteArrayInputStream(signatureData)) { + PGPSignature signature = parseSignature(is); + if (signature != null) { + GpgSigningKey signingKey = loadKey(signature.getKeyID()); + if (signingKey != null) { + if (signingKey.getEmailAddresses() != null + && !signingKey.getEmailAddresses().contains(emailAddress)) { + return new GpgVerificationFailed(signingKey, "Not a verified email of signing GPG key"); } + signature.init( + new JcaPGPContentVerifierBuilderProvider().setProvider(BouncyCastleProvider.PROVIDER_NAME), + signingKey.getPublicKey()); + signature.update(data); + if (signature.verify()) + return new GpgVerificationSuccessful(signingKey); + else + return new GpgVerificationFailed(signingKey, "Invalid GPG signature"); } else { - return new GpgVerificationFailed(null, "Looks like a GPG signature but without necessary data"); + return new GpgVerificationFailed(null, "Signed with an unknown GPG key " + + "(key ID: " + GpgUtils.getKeyIDString(signature.getKeyID()) + ")"); } - } catch (PGPException e) { - logger.error("Error verifying GPG signature", e); - return new GpgVerificationFailed(null, "Error verifying GPG signature"); - } catch (IOException e) { - throw new RuntimeException(e); + } else { + return new GpgVerificationFailed(null, "Looks like a GPG signature but without necessary data"); } - } else { - return null; + } catch (PGPException e) { + logger.error("Error verifying GPG signature", e); + return new GpgVerificationFailed(null, "Error verifying GPG signature"); + } catch (IOException e) { + throw new RuntimeException(e); } } - + + @Override + public String getPrefix() { + return "-----BEGIN PGP SIGNATURE-----"; + } + } diff --git a/server-core/src/main/java/io/onedev/server/git/signatureverification/ssh/SshSignatureVerifier.java b/server-core/src/main/java/io/onedev/server/git/signatureverification/ssh/SshSignatureVerifier.java index fbe5911ab3..b8fa41b9d7 100644 --- a/server-core/src/main/java/io/onedev/server/git/signatureverification/ssh/SshSignatureVerifier.java +++ b/server-core/src/main/java/io/onedev/server/git/signatureverification/ssh/SshSignatureVerifier.java @@ -14,22 +14,17 @@ import org.bouncycastle.crypto.digests.SHA512Digest; import org.bouncycastle.crypto.signers.Ed25519Signer; import org.bouncycastle.crypto.signers.RSADigestSigner; import org.bouncycastle.util.io.pem.PemReader; -import org.eclipse.jgit.util.RawParseUtils; -import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Singleton; import java.io.StringReader; -import static java.nio.charset.StandardCharsets.UTF_8; import static org.apache.sshd.common.digest.DigestUtils.getFingerPrint; import static org.bouncycastle.crypto.util.OpenSSHPublicKeyUtil.parsePublicKey; @Singleton public class SshSignatureVerifier implements SignatureVerifier { - private static final byte[] SIGNATURE_START = "-----BEGIN SSH SIGNATURE-----\n".getBytes(UTF_8); - private final SshKeyManager sshKeyManager; private final EmailAddressManager emailAddressManager; @@ -40,89 +35,89 @@ public class SshSignatureVerifier implements SignatureVerifier { this.emailAddressManager = emailAddressManager; } - @Nullable @Override 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)))) { - var signatureBlob = pemReader.readPemObject().getContent(); - var signatureBlobReader = new TypesReader(signatureBlob); - var magicPreamble = new String(signatureBlobReader.readBytes(6)); - if (!magicPreamble.equals("SSHSIG")) - return new SshVerificationFailed(null, "Malformed ssh signature"); - var version = signatureBlobReader.readUINT32(); - if (version != 1) - return new SshVerificationFailed(null, "Unsupported ssh signature version: " + version); - var publicKeyBytes = signatureBlobReader.readByteString(); - var keyType = new TypesReader(publicKeyBytes).readString(); - var fingerprint = getFingerPrint(BuiltinDigests.sha256, publicKeyBytes); - var publicKey = parsePublicKey(publicKeyBytes); - var keyInfo = new SshKeyInfo(keyType, fingerprint); - - var sshKey = sshKeyManager.findByDigest(fingerprint); - if (sshKey == null) - return new SshVerificationFailed(keyInfo, "Signed with an unknown ssh key"); - - var emailAddress = emailAddressManager.findByValue(emailAddressValue); - if (emailAddress == null - || !emailAddress.isVerified() - || !emailAddress.getOwner().equals(sshKey.getOwner())) { - return new SshVerificationFailed(keyInfo, "Not a verified email of signing ssh key owner"); - } - - var namespace = signatureBlobReader.readString(); - if (!namespace.equals("git")) - return new SshVerificationFailed(keyInfo, "Unexpected ssh signature namespace: " + namespace); - var reserved = signatureBlobReader.readString(); - - var hashAlgorithm = signatureBlobReader.readString(); - byte[] hash; - if (hashAlgorithm.equals("sha256")) - hash = org.apache.commons.codec.digest.DigestUtils.sha256(data); - else if (hashAlgorithm.equals("sha512")) - hash = org.apache.commons.codec.digest.DigestUtils.sha512(data); - else - return new SshVerificationFailed(keyInfo, "Unexpected ssh signature hash algorithm: " + hashAlgorithm); - - var writer = new TypesWriter(); - writer.writeBytes(magicPreamble.getBytes()); - writer.writeString(namespace); - writer.writeString(reserved); - writer.writeString(hashAlgorithm); - writer.writeString(hash, 0, hash.length); - - var sshSignature = signatureBlobReader.readByteString(); - - var sshSignatureReader = new TypesReader(sshSignature); - var signatureAlgorithm = sshSignatureReader.readString(); - Signer signer; - switch (signatureAlgorithm) { - case "rsa-sha2-256": - signer = new RSADigestSigner(new SHA256Digest()); - break; - case "rsa-sha2-512": - signer = new RSADigestSigner(new SHA512Digest()); - break; - case "ssh-ed25519": - signer = new Ed25519Signer(); - break; - default: - return new SshVerificationFailed(keyInfo, "Unsupported ssh signature algorithm: " + signatureAlgorithm); - } - - signer.init(false, publicKey); - var signBytes = writer.getBytes(); - signer.update(signBytes, 0, signBytes.length); - if (signer.verifySignature(sshSignatureReader.readByteString())) - return new SshVerificationSuccessful(keyInfo); - else - return new SshVerificationFailed(keyInfo, "Invalid ssh signature"); - } catch (Exception e) { - throw ExceptionUtils.unchecked(e); + try (var pemReader = new PemReader(new StringReader(new String(signatureData)))) { + var signatureBlob = pemReader.readPemObject().getContent(); + var signatureBlobReader = new TypesReader(signatureBlob); + var magicPreamble = new String(signatureBlobReader.readBytes(6)); + if (!magicPreamble.equals("SSHSIG")) + return new SshVerificationFailed(null, "Malformed ssh signature"); + var version = signatureBlobReader.readUINT32(); + if (version != 1) + return new SshVerificationFailed(null, "Unsupported ssh signature version: " + version); + var publicKeyBytes = signatureBlobReader.readByteString(); + var keyType = new TypesReader(publicKeyBytes).readString(); + var fingerprint = getFingerPrint(BuiltinDigests.sha256, publicKeyBytes); + var publicKey = parsePublicKey(publicKeyBytes); + var keyInfo = new SshKeyInfo(keyType, fingerprint); + + var sshKey = sshKeyManager.findByFingerprint(fingerprint); + if (sshKey == null) + return new SshVerificationFailed(keyInfo, "Signed with an unknown ssh key"); + + var emailAddress = emailAddressManager.findByValue(emailAddressValue); + if (emailAddress == null + || !emailAddress.isVerified() + || !emailAddress.getOwner().equals(sshKey.getOwner())) { + return new SshVerificationFailed(keyInfo, "Not a verified email of signing ssh key owner"); } - } else { - return null; + + var namespace = signatureBlobReader.readString(); + if (!namespace.equals("git")) + return new SshVerificationFailed(keyInfo, "Unexpected ssh signature namespace: " + namespace); + var reserved = signatureBlobReader.readString(); + + var hashAlgorithm = signatureBlobReader.readString(); + byte[] hash; + if (hashAlgorithm.equals("sha256")) + hash = org.apache.commons.codec.digest.DigestUtils.sha256(data); + else if (hashAlgorithm.equals("sha512")) + hash = org.apache.commons.codec.digest.DigestUtils.sha512(data); + else + return new SshVerificationFailed(keyInfo, "Unexpected ssh signature hash algorithm: " + hashAlgorithm); + + var writer = new TypesWriter(); + writer.writeBytes(magicPreamble.getBytes()); + writer.writeString(namespace); + writer.writeString(reserved); + writer.writeString(hashAlgorithm); + writer.writeString(hash, 0, hash.length); + + var sshSignature = signatureBlobReader.readByteString(); + + var sshSignatureReader = new TypesReader(sshSignature); + var signatureAlgorithm = sshSignatureReader.readString(); + Signer signer; + switch (signatureAlgorithm) { + case "rsa-sha2-256": + signer = new RSADigestSigner(new SHA256Digest()); + break; + case "rsa-sha2-512": + signer = new RSADigestSigner(new SHA512Digest()); + break; + case "ssh-ed25519": + signer = new Ed25519Signer(); + break; + default: + return new SshVerificationFailed(keyInfo, "Unsupported ssh signature algorithm: " + signatureAlgorithm); + } + + signer.init(false, publicKey); + var signBytes = writer.getBytes(); + signer.update(signBytes, 0, signBytes.length); + if (signer.verifySignature(sshSignatureReader.readByteString())) + return new SshVerificationSuccessful(keyInfo); + else + return new SshVerificationFailed(keyInfo, "Invalid ssh signature"); + } catch (Exception e) { + throw ExceptionUtils.unchecked(e); } } - + + @Override + public String getPrefix() { + return "-----BEGIN SSH SIGNATURE-----"; + } + } diff --git a/server-core/src/main/java/io/onedev/server/model/support/administration/GpgSetting.java b/server-core/src/main/java/io/onedev/server/model/support/administration/GpgSetting.java index b3ba6b961d..3f55784471 100644 --- a/server-core/src/main/java/io/onedev/server/model/support/administration/GpgSetting.java +++ b/server-core/src/main/java/io/onedev/server/model/support/administration/GpgSetting.java @@ -117,7 +117,7 @@ public class GpgSetting implements Serializable { } @Nullable - public GpgSigningKey findSignatureVerificationKey(long keyId) { + public GpgSigningKey findSigningKey(long keyId) { if (getSigningKey() != null && getSigningKey().getPublicKey().getKeyID() == keyId) { return new GpgSigningKey() { diff --git a/server-core/src/main/java/io/onedev/server/ssh/DefaultSshAuthenticator.java b/server-core/src/main/java/io/onedev/server/ssh/DefaultSshAuthenticator.java index ad462b3560..5789d58621 100644 --- a/server-core/src/main/java/io/onedev/server/ssh/DefaultSshAuthenticator.java +++ b/server-core/src/main/java/io/onedev/server/ssh/DefaultSshAuthenticator.java @@ -47,7 +47,7 @@ public class DefaultSshAuthenticator implements SshAuthenticator { } String digest = KeyUtils.getFingerPrint(BuiltinDigests.sha256, key); - SshKey sshKey = sshKeyManager.findByDigest(digest); + SshKey sshKey = sshKeyManager.findByFingerprint(digest); if (sshKey != null) { session.setAttribute(ATTR_PUBLIC_KEY_OWNER_ID, sshKey.getOwner().getId()); return true; diff --git a/server-core/src/main/java/io/onedev/server/web/component/commit/info/CommitInfoPanel.java b/server-core/src/main/java/io/onedev/server/web/component/commit/info/CommitInfoPanel.java index b3402463af..9504c77589 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/commit/info/CommitInfoPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/commit/info/CommitInfoPanel.java @@ -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.status.CommitStatusLink; 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.copytoclipboard.CopyToClipboardLink; import io.onedev.server.web.component.user.contributoravatars.ContributorAvatars; @@ -80,7 +80,7 @@ public abstract class CommitInfoPanel extends GenericPanel }); - add(new VerificationResultPanel("signature") { + add(new SignatureStatusPanel("signature") { @Override protected RevObject getRevObject() { diff --git a/server-core/src/main/java/io/onedev/server/web/component/commit/list/CommitListPanel.java b/server-core/src/main/java/io/onedev/server/web/component/commit/list/CommitListPanel.java index 209823b366..71b5b2369d 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/commit/list/CommitListPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/commit/list/CommitListPanel.java @@ -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.CommitStatusSupport; 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.copytoclipboard.CopyToClipboardLink; import io.onedev.server.web.component.savedquery.SavedQueriesClosed; @@ -641,7 +641,7 @@ public abstract class CommitListPanel extends Panel { getCommitIdsToQueryStatus().add(commit.copy()); - item.add(new VerificationResultPanel("signature") { + item.add(new SignatureStatusPanel("signature") { @Override protected RevObject getRevObject() { diff --git a/server-core/src/main/java/io/onedev/server/web/component/gitsignature/VerificationResultPanel.java b/server-core/src/main/java/io/onedev/server/web/component/gitsignature/SignatureStatusPanel.java similarity index 95% rename from server-core/src/main/java/io/onedev/server/web/component/gitsignature/VerificationResultPanel.java rename to server-core/src/main/java/io/onedev/server/web/component/gitsignature/SignatureStatusPanel.java index 0a049d523e..10a8c29d61 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/gitsignature/VerificationResultPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/gitsignature/SignatureStatusPanel.java @@ -15,7 +15,7 @@ import org.apache.wicket.model.LoadableDetachableModel; import org.eclipse.jgit.revwalk.RevObject; @SuppressWarnings("serial") -public abstract class VerificationResultPanel extends DropdownLink { +public abstract class SignatureStatusPanel extends DropdownLink { private IModel model = new LoadableDetachableModel<>() { @@ -26,7 +26,7 @@ public abstract class VerificationResultPanel extends DropdownLink { }; - public VerificationResultPanel(String id) { + public SignatureStatusPanel(String id) { super(id); } diff --git a/server-core/src/main/java/io/onedev/server/web/component/user/gpgkey/InsertGpgKeyPanel.java b/server-core/src/main/java/io/onedev/server/web/component/user/gpgkey/InsertGpgKeyPanel.java index d48be7410b..c8f3852d17 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/user/gpgkey/InsertGpgKeyPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/user/gpgkey/InsertGpgKeyPanel.java @@ -59,7 +59,7 @@ public abstract class InsertGpgKeyPanel extends Panel { gpgKey.setOwner(getUser()); 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"); target.add(form); } else { diff --git a/server-core/src/main/java/io/onedev/server/web/component/user/sshkey/InsertSshKeyPanel.java b/server-core/src/main/java/io/onedev/server/web/component/user/sshkey/InsertSshKeyPanel.java index 1c339e49d5..e1877b4e89 100644 --- a/server-core/src/main/java/io/onedev/server/web/component/user/sshkey/InsertSshKeyPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/component/user/sshkey/InsertSshKeyPanel.java @@ -54,7 +54,7 @@ public abstract class InsertSshKeyPanel extends Panel { SshKey sshKey = (SshKey) editor.getModelObject(); 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"); target.add(form); } else { diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/commits/CommitDetailPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/commits/CommitDetailPage.java index 9ca3ab0d51..50207c00c6 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/commits/CommitDetailPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/commits/CommitDetailPage.java @@ -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.createtag.CreateTagLink; 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.link.ViewStateAwarePageLink; 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 ContributorPanel("contribution", getCommit().getAuthorIdent(), getCommit().getCommitterIdent())); - add(new VerificationResultPanel("signature") { + add(new SignatureStatusPanel("signature") { @Override protected RevObject getRevObject() { diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/pullrequests/detail/activities/activity/PullRequestUpdatedPanel.java b/server-core/src/main/java/io/onedev/server/web/page/project/pullrequests/detail/activities/activity/PullRequestUpdatedPanel.java index 6855372d7d..b1b80a5343 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/pullrequests/detail/activities/activity/PullRequestUpdatedPanel.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/pullrequests/detail/activities/activity/PullRequestUpdatedPanel.java @@ -10,7 +10,7 @@ import io.onedev.server.util.DateUtils; import io.onedev.server.web.WebConstants; import io.onedev.server.web.behavior.WebSocketObserver; 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.copytoclipboard.CopyToClipboardLink; import io.onedev.server.web.component.user.ident.Mode; @@ -90,7 +90,7 @@ class PullRequestUpdatedPanel extends GenericPanel { }); - item.add(new VerificationResultPanel("signature") { + item.add(new SignatureStatusPanel("signature") { @Override protected RevObject getRevObject() { diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/tags/ProjectTagsPage.java b/server-core/src/main/java/io/onedev/server/web/page/project/tags/ProjectTagsPage.java index ee0c9f081b..d179cf28a9 100644 --- a/server-core/src/main/java/io/onedev/server/web/page/project/tags/ProjectTagsPage.java +++ b/server-core/src/main/java/io/onedev/server/web/page/project/tags/ProjectTagsPage.java @@ -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.contributorpanel.ContributorPanel; 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.ViewStateAwarePageLink; import io.onedev.server.web.component.modal.ModalLink; @@ -338,7 +338,7 @@ public class ProjectTagsPage extends ProjectPage { }); if (ref.getObj() instanceof RevTag) { - fragment.add(new VerificationResultPanel("signature") { + fragment.add(new SignatureStatusPanel("signature") { @Override protected RevObject getRevObject() { diff --git a/server-core/src/main/java/org/eclipse/jgit/revwalk/RevTag.java b/server-core/src/main/java/org/eclipse/jgit/revwalk/RevTag.java index b9d145008b..b5d74ba747 100644 --- a/server-core/src/main/java/org/eclipse/jgit/revwalk/RevTag.java +++ b/server-core/src/main/java/org/eclipse/jgit/revwalk/RevTag.java @@ -39,7 +39,7 @@ import org.eclipse.jgit.util.StringUtils; public class RevTag extends RevObject { 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. diff --git a/server-core/src/test/java/io/onedev/server/git/signatureverification/ssh/SignatureVerifierTest.java b/server-core/src/test/java/io/onedev/server/git/signatureverification/ssh/SignatureVerifierTest.java new file mode 100644 index 0000000000..e65912e3e6 --- /dev/null +++ b/server-core/src/test/java/io/onedev/server/git/signatureverification/ssh/SignatureVerifierTest.java @@ -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 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); + } + } + +} \ No newline at end of file diff --git a/server-core/src/test/java/io/onedev/server/git/signatureverification/ssh/git-signature.zip b/server-core/src/test/java/io/onedev/server/git/signatureverification/ssh/git-signature.zip new file mode 100644 index 0000000000..bbc01576b4 Binary files /dev/null and b/server-core/src/test/java/io/onedev/server/git/signatureverification/ssh/git-signature.zip differ