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> {
|
||||
|
||||
@Nullable
|
||||
GpgSigningKey findSignatureVerificationKey(long keyId);
|
||||
GpgSigningKey findSigningKey(long keyId);
|
||||
|
||||
void create(GpgKey gpgKey);
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ import io.onedev.server.persistence.dao.EntityManager;
|
||||
public interface SshKeyManager extends EntityManager<SshKey> {
|
||||
|
||||
@Nullable
|
||||
SshKey findByDigest(String digest);
|
||||
SshKey findByFingerprint(String fingerprint);
|
||||
|
||||
void syncSshKeys(User user, Collection<String> sshKeys);
|
||||
|
||||
|
||||
@ -96,7 +96,7 @@ public class DefaultGpgKeyManager extends BaseEntityManager<GpgKey> 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() {
|
||||
|
||||
@ -38,8 +38,8 @@ public class DefaultSshKeyManager extends BaseEntityManager<SshKey> 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<SshKey> entityCriteria = EntityCriteria.of(SshKey.class).add(eq);
|
||||
entityCriteria.setCacheable(true);
|
||||
return find(entityCriteria);
|
||||
@ -66,7 +66,7 @@ public class DefaultSshKeyManager extends BaseEntityManager<SshKey> 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());
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
}
|
||||
|
||||
@ -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-----";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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-----";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<ProjectScopedCommit>
|
||||
|
||||
});
|
||||
|
||||
add(new VerificationResultPanel("signature") {
|
||||
add(new SignatureStatusPanel("signature") {
|
||||
|
||||
@Override
|
||||
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.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() {
|
||||
|
||||
@ -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<VerificationResult> model = new LoadableDetachableModel<>() {
|
||||
|
||||
@ -26,7 +26,7 @@ public abstract class VerificationResultPanel extends DropdownLink {
|
||||
|
||||
};
|
||||
|
||||
public VerificationResultPanel(String id) {
|
||||
public SignatureStatusPanel(String id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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<PullRequestUpdate> {
|
||||
|
||||
});
|
||||
|
||||
item.add(new VerificationResultPanel("signature") {
|
||||
item.add(new SignatureStatusPanel("signature") {
|
||||
|
||||
@Override
|
||||
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.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() {
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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