* master: (143 commits) Prepare 5.11.0-SNAPSHOT builds JGit v5.11.0.202102240950-m3 [releng] japicmp: update last release version IgnoreNode: include path to file for invalid .gitignore patterns FastIgnoreRule: include bad pattern in log message init: add config option to set default for the initial branch name init: allow specifying the initial branch name for the new repository Fail clone if initial branch doesn't exist in remote repository GPG: fix reading unprotected old-format secret keys Update Orbit to S20210216215844 Add missing bazel dependency for o.e.j.gpg.bc.test GPG: handle extended private key format dfs: handle short copies [GPG] Provide a factory for the BouncyCastleGpgSigner Fix boxing warnings GPG: compute the keygrip to find a secret key GPG signature verification via BouncyCastle Post commit hook failure should not cause commit failure Allow to define additional Hook classes outside JGit GitHook: use default charset for output and error streams ... Change-Id: I689f4070e79f4a0ac1c02b35698ccaab68ad2f34changes/17/176917/2
@@ -1 +1 @@ | |||
3.5.0 | |||
4.0.0 |
@@ -13,6 +13,8 @@ genrule( | |||
"//org.eclipse.jgit.lfs:jgit-lfs", | |||
"//org.eclipse.jgit.lfs.server:jgit-lfs-server", | |||
"//org.eclipse.jgit.junit:junit", | |||
"//org.eclipse.jgit.ssh.apache:ssh-apache", | |||
"//org.eclipse.jgit.ssh.jsch:ssh-jsch", | |||
], | |||
outs = ["all.zip"], | |||
cmd = " && ".join([ |
@@ -27,6 +27,34 @@ load( | |||
"maven_jar", | |||
) | |||
http_archive( | |||
name = "openjdk15_linux_archive", | |||
build_file_content = """ | |||
java_runtime(name = 'runtime', srcs = glob(['**']), visibility = ['//visibility:public']) | |||
exports_files(["WORKSPACE"], visibility = ["//visibility:public"]) | |||
""", | |||
sha256 = "0a38f1138c15a4f243b75eb82f8ef40855afcc402e3c2a6de97ce8235011b1ad", | |||
strip_prefix = "zulu15.27.17-ca-jdk15.0.0-linux_x64", | |||
urls = [ | |||
"https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu15.27.17-ca-jdk15.0.0-linux_x64.tar.gz", | |||
"https://cdn.azul.com/zulu/bin/zulu15.27.17-ca-jdk15.0.0-linux_x64.tar.gz", | |||
], | |||
) | |||
http_archive( | |||
name = "openjdk15_darwin_archive", | |||
build_file_content = """ | |||
java_runtime(name = 'runtime', srcs = glob(['**']), visibility = ['//visibility:public']) | |||
exports_files(["WORKSPACE"], visibility = ["//visibility:public"]) | |||
""", | |||
sha256 = "f80b2e0512d9d8a92be24497334c974bfecc8c898fc215ce0e76594f00437482", | |||
strip_prefix = "zulu15.27.17-ca-jdk15.0.0-macosx_x64", | |||
urls = [ | |||
"https://mirror.bazel.build/cdn.azul.com/zulu/bin/zulu15.27.17-ca-jdk15.0.0-macosx_x64.tar.gz", | |||
"https://cdn.azul.com/zulu/bin/zulu15.27.17-ca-jdk15.0.0-macosx_x64.tar.gz", | |||
], | |||
) | |||
JMH_VERS = "1.21" | |||
maven_jar( | |||
@@ -83,26 +111,26 @@ maven_jar( | |||
maven_jar( | |||
name = "httpclient", | |||
artifact = "org.apache.httpcomponents:httpclient:4.5.10", | |||
sha1 = "7ca2e4276f4ef95e4db725a8cd4a1d1e7585b9e5", | |||
artifact = "org.apache.httpcomponents:httpclient:4.5.13", | |||
sha1 = "e5f6cae5ca7ecaac1ec2827a9e2d65ae2869cada", | |||
) | |||
maven_jar( | |||
name = "httpcore", | |||
artifact = "org.apache.httpcomponents:httpcore:4.4.12", | |||
sha1 = "21ebaf6d532bc350ba95bd81938fa5f0e511c132", | |||
artifact = "org.apache.httpcomponents:httpcore:4.4.14", | |||
sha1 = "9dd1a631c082d92ecd4bd8fd4cf55026c720a8c1", | |||
) | |||
maven_jar( | |||
name = "sshd-osgi", | |||
artifact = "org.apache.sshd:sshd-osgi:2.4.0", | |||
sha1 = "fc4551c1eeda35e4671b263297d37d2bca81c4d4", | |||
artifact = "org.apache.sshd:sshd-osgi:2.6.0", | |||
sha1 = "40e365bb799e1bff3d31dc858b1e59a93c123f29", | |||
) | |||
maven_jar( | |||
name = "sshd-sftp", | |||
artifact = "org.apache.sshd:sshd-sftp:2.4.0", | |||
sha1 = "92e1b7d1e19c715efb4a8871d34145da8f87cdb2", | |||
artifact = "org.apache.sshd:sshd-sftp:2.6.0", | |||
sha1 = "6eddfe8fdf59a3d9a49151e4177f8c1bebeb30c9", | |||
) | |||
maven_jar( | |||
@@ -205,52 +233,59 @@ maven_jar( | |||
maven_jar( | |||
name = "gson", | |||
artifact = "com.google.code.gson:gson:2.8.2", | |||
sha1 = "3edcfe49d2c6053a70a2a47e4e1c2f94998a49cf", | |||
artifact = "com.google.code.gson:gson:2.8.6", | |||
sha1 = "9180733b7df8542621dc12e21e87557e8c99b8cb", | |||
) | |||
JETTY_VER = "9.4.30.v20200611" | |||
JETTY_VER = "9.4.36.v20210114" | |||
maven_jar( | |||
name = "jetty-servlet", | |||
artifact = "org.eclipse.jetty:jetty-servlet:" + JETTY_VER, | |||
sha1 = "ca3dea2cd34ee88cec017001603af0c9e74781d6", | |||
src_sha1 = "6908f24428060bd542bddfa3e89e03d0dbbc2a6d", | |||
sha1 = "b189e52a5ee55ae172e4e99e29c5c314f5daf4b9", | |||
src_sha1 = "3a0fa449772ab0d76625f6afb81f60c32a490613", | |||
) | |||
maven_jar( | |||
name = "jetty-security", | |||
artifact = "org.eclipse.jetty:jetty-security:" + JETTY_VER, | |||
sha1 = "1a5261f6ad4081ad9e9bb01416d639931d391273", | |||
src_sha1 = "6ca41b34aa4f84c267603edd4b069122bd5f17d3", | |||
sha1 = "42030d6ed7dfc0f75818cde0adcf738efc477574", | |||
src_sha1 = "612220a97d45fad3983ccc56b0cb9a271f3fd003", | |||
) | |||
maven_jar( | |||
name = "jetty-server", | |||
artifact = "org.eclipse.jetty:jetty-server:" + JETTY_VER, | |||
sha1 = "e5ede3724d062717d0c04e4c77f74fe8115c2a6f", | |||
src_sha1 = "c8b02a47a35c1f083b310cbd202738cf08bc1d55", | |||
sha1 = "88a7d342974aadca658e7386e8d0fcc5c0788f41", | |||
src_sha1 = "4552c0c6db2948e8557db477b6b48d291006e481", | |||
) | |||
maven_jar( | |||
name = "jetty-http", | |||
artifact = "org.eclipse.jetty:jetty-http:" + JETTY_VER, | |||
sha1 = "cd6223382e4f82b9ea807d8cdb04a23e5d629f1c", | |||
src_sha1 = "00520c04b10609b981159b5ca284b5a158c077a9", | |||
sha1 = "1eee89a55e04ff94df0f85d95200fc48acb43d86", | |||
src_sha1 = "552a784ec789c7ba581c5341ae6d8b6353ed5ace", | |||
) | |||
maven_jar( | |||
name = "jetty-io", | |||
artifact = "org.eclipse.jetty:jetty-io:" + JETTY_VER, | |||
sha1 = "9c360d08e903b2dbd5d1f8e889a32046948628ce", | |||
src_sha1 = "dac8f8a3f84afdd3686d36f58b5ccb276961b8ce", | |||
sha1 = "84a8faf9031eb45a5a2ddb7681e22c483d81ab3a", | |||
src_sha1 = "72d5fc6d909e28f8720394b25babda80805a46b9", | |||
) | |||
maven_jar( | |||
name = "jetty-util", | |||
artifact = "org.eclipse.jetty:jetty-util:" + JETTY_VER, | |||
sha1 = "39ec6aa4745952077f5407cb1394d8ba2db88b13", | |||
src_sha1 = "f41f9391f91884a79350f3ad9b09b8e46c9be0ec", | |||
sha1 = "925257fbcca6b501a25252c7447dbedb021f7404", | |||
src_sha1 = "532e8b66044f4e58ca5da3aec19f02a2f3c16ddd", | |||
) | |||
maven_jar( | |||
name = "jetty-util-ajax", | |||
artifact = "org.eclipse.jetty:jetty-util-ajax:" + JETTY_VER, | |||
sha1 = "2f478130c21787073facb64d7242e06f94980c60", | |||
src_sha1 = "7153d7ca38878d971fd90992c303bb7719ba7a21", | |||
) | |||
BOUNCYCASTLE_VER = "1.65" |
@@ -132,7 +132,10 @@ java_library( | |||
name = "jetty-servlet", | |||
# TODO: This should be testonly but org.eclipse.jgit.pgm depends on it. | |||
visibility = ["//visibility:public"], | |||
exports = ["@jetty-servlet//jar"], | |||
exports = [ | |||
"@jetty-servlet//jar", | |||
"@jetty-util-ajax//jar", | |||
], | |||
) | |||
java_library( | |||
@@ -159,6 +162,7 @@ java_library( | |||
"//org.eclipse.jgit:__pkg__", | |||
"//org.eclipse.jgit.gpg.bc:__pkg__", | |||
"//org.eclipse.jgit.test:__pkg__", | |||
"//org.eclipse.jgit.gpg.bc.test:__pkg__", | |||
], | |||
exports = ["@bcpg//jar"], | |||
) | |||
@@ -169,6 +173,7 @@ java_library( | |||
"//org.eclipse.jgit:__pkg__", | |||
"//org.eclipse.jgit.gpg.bc:__pkg__", | |||
"//org.eclipse.jgit.test:__pkg__", | |||
"//org.eclipse.jgit.gpg.bc.test:__pkg__", | |||
], | |||
exports = ["@bcprov//jar"], | |||
) |
@@ -51,8 +51,8 @@ org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTags=error | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=no_tag | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTags=ignore | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private |
@@ -5,3 +5,4 @@ Bundle-SymbolicName: org.eclipse.jgit.ant.source | |||
Bundle-Vendor: Eclipse.org - JGit | |||
Bundle-Version: 6.0.0.qualifier | |||
Eclipse-SourceBundle: org.eclipse.jgit.ant;version="6.0.0.qualifier";roots="." | |||
@@ -0,0 +1,11 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<component id="org.eclipse.jgit.archive" version="2"> | |||
<resource path="src/org/eclipse/jgit/archive/BaseFormat.java" type="org.eclipse.jgit.archive.BaseFormat"> | |||
<filter id="336658481"> | |||
<message_arguments> | |||
<message_argument value="org.eclipse.jgit.archive.BaseFormat"/> | |||
<message_argument value="COMPRESSION_LEVEL"/> | |||
</message_arguments> | |||
</filter> | |||
</resource> | |||
</component> |
@@ -27,3 +27,4 @@ Export-Package: org.eclipse.jgit.archive;version="6.0.0"; | |||
org.apache.commons.compress.archivers, | |||
org.osgi.framework", | |||
org.eclipse.jgit.archive.internal;version="6.0.0";x-internal:=true | |||
@@ -5,3 +5,4 @@ Bundle-SymbolicName: org.eclipse.jgit.archive.source | |||
Bundle-Vendor: Eclipse.org - JGit | |||
Bundle-Version: 6.0.0.qualifier | |||
Eclipse-SourceBundle: org.eclipse.jgit.archive;version="6.0.0.qualifier";roots="." | |||
@@ -1,3 +1,4 @@ | |||
cannotSetOption=Cannot set option: {0} | |||
invalidCompressionLevel=Invalid compression level: {0} | |||
pathDoesNotMatchMode=Path {0} does not match mode {1} | |||
unsupportedMode=Unsupported mode {0} |
@@ -25,6 +25,11 @@ import org.eclipse.jgit.util.StringUtils; | |||
* @since 4.0 | |||
*/ | |||
public class BaseFormat { | |||
/** | |||
* Compression-level for the archive file. Only values in [0-9] are allowed. | |||
* @since 5.11 | |||
*/ | |||
protected static final String COMPRESSION_LEVEL = "compression-level"; //$NON-NLS-1$ | |||
/** | |||
* Apply options to archive output stream | |||
@@ -40,6 +45,9 @@ public class BaseFormat { | |||
Map<String, Object> o) throws IOException { | |||
for (Map.Entry<String, Object> p : o.entrySet()) { | |||
try { | |||
if (p.getKey().equals(COMPRESSION_LEVEL)) { | |||
continue; | |||
} | |||
new Statement(s, "set" + StringUtils.capitalize(p.getKey()), //$NON-NLS-1$ | |||
new Object[] { p.getValue() }).execute(); | |||
} catch (Exception e) { | |||
@@ -49,4 +57,32 @@ public class BaseFormat { | |||
} | |||
return s; | |||
} | |||
/** | |||
* Removes and returns the {@link #COMPRESSION_LEVEL} key from the input map | |||
* parameter if it exists, or -1 if this key does not exist. | |||
* | |||
* @param o | |||
* options map | |||
* @return The compression level if it exists in the map, or -1 instead. | |||
* @throws IllegalArgumentException | |||
* if the {@link #COMPRESSION_LEVEL} option does not parse to an | |||
* Integer. | |||
* @since 5.11 | |||
*/ | |||
protected int getCompressionLevel(Map<String, Object> o) { | |||
if (!o.containsKey(COMPRESSION_LEVEL)) { | |||
return -1; | |||
} | |||
Object option = o.get(COMPRESSION_LEVEL); | |||
try { | |||
Integer compressionLevel = (Integer) option; | |||
return compressionLevel.intValue(); | |||
} catch (ClassCastException e) { | |||
throw new IllegalArgumentException( | |||
MessageFormat.format( | |||
ArchiveText.get().invalidCompressionLevel, option), | |||
e); | |||
} | |||
} | |||
} |
@@ -45,7 +45,13 @@ public final class Tbz2Format extends BaseFormat implements | |||
@Override | |||
public ArchiveOutputStream createArchiveOutputStream(OutputStream s, | |||
Map<String, Object> o) throws IOException { | |||
BZip2CompressorOutputStream out = new BZip2CompressorOutputStream(s); | |||
BZip2CompressorOutputStream out; | |||
int compressionLevel = getCompressionLevel(o); | |||
if (compressionLevel != -1) { | |||
out = new BZip2CompressorOutputStream(s, compressionLevel); | |||
} else { | |||
out = new BZip2CompressorOutputStream(s); | |||
} | |||
return tarFormat.createArchiveOutputStream(out, o); | |||
} | |||
@@ -18,6 +18,7 @@ import java.util.Map; | |||
import org.apache.commons.compress.archivers.ArchiveOutputStream; | |||
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; | |||
import org.apache.commons.compress.compressors.gzip.GzipParameters; | |||
import org.eclipse.jgit.api.ArchiveCommand; | |||
import org.eclipse.jgit.lib.FileMode; | |||
import org.eclipse.jgit.lib.ObjectId; | |||
@@ -45,7 +46,15 @@ public final class TgzFormat extends BaseFormat implements | |||
@Override | |||
public ArchiveOutputStream createArchiveOutputStream(OutputStream s, | |||
Map<String, Object> o) throws IOException { | |||
GzipCompressorOutputStream out = new GzipCompressorOutputStream(s); | |||
GzipCompressorOutputStream out; | |||
int compressionLevel = getCompressionLevel(o); | |||
if (compressionLevel != -1) { | |||
GzipParameters parameters = new GzipParameters(); | |||
parameters.setCompressionLevel(compressionLevel); | |||
out = new GzipCompressorOutputStream(s, parameters); | |||
} else { | |||
out = new GzipCompressorOutputStream(s); | |||
} | |||
return tarFormat.createArchiveOutputStream(out, o); | |||
} | |||
@@ -45,7 +45,13 @@ public final class TxzFormat extends BaseFormat implements | |||
@Override | |||
public ArchiveOutputStream createArchiveOutputStream(OutputStream s, | |||
Map<String, Object> o) throws IOException { | |||
XZCompressorOutputStream out = new XZCompressorOutputStream(s); | |||
XZCompressorOutputStream out; | |||
int compressionLevel = getCompressionLevel(o); | |||
if (compressionLevel != -1) { | |||
out = new XZCompressorOutputStream(s, compressionLevel); | |||
} else { | |||
out = new XZCompressorOutputStream(s); | |||
} | |||
return tarFormat.createArchiveOutputStream(out, o); | |||
} | |||
@@ -47,7 +47,12 @@ public final class ZipFormat extends BaseFormat implements | |||
@Override | |||
public ArchiveOutputStream createArchiveOutputStream(OutputStream s, | |||
Map<String, Object> o) throws IOException { | |||
return applyFormatOptions(new ZipArchiveOutputStream(s), o); | |||
ZipArchiveOutputStream out = new ZipArchiveOutputStream(s); | |||
int compressionLevel = getCompressionLevel(o); | |||
if (compressionLevel != -1) { | |||
out.setLevel(compressionLevel); | |||
} | |||
return applyFormatOptions(out, o); | |||
} | |||
/** {@inheritDoc} */ |
@@ -28,6 +28,7 @@ public class ArchiveText extends TranslationBundle { | |||
// @formatter:off | |||
/***/ public String cannotSetOption; | |||
/***/ public String invalidCompressionLevel; | |||
/***/ public String pathDoesNotMatchMode; | |||
/***/ public String unsupportedMode; | |||
} |
@@ -0,0 +1,145 @@ | |||
#SpotBugs User Preferences | |||
#Fri Dec 04 10:39:51 CET 2020 | |||
detectorExplicitSerialization=ExplicitSerialization|true | |||
detectorMultithreadedInstanceAccess=MultithreadedInstanceAccess|true | |||
detectorConfusionBetweenInheritedAndOuterMethod=ConfusionBetweenInheritedAndOuterMethod|true | |||
detectorWrongMapIterator=WrongMapIterator|true | |||
detectorUnnecessaryMath=UnnecessaryMath|true | |||
detectorUselessSubclassMethod=UselessSubclassMethod|false | |||
filter_settings=Medium|BAD_PRACTICE,CORRECTNESS,EXPERIMENTAL,I18N,MALICIOUS_CODE,MT_CORRECTNESS,PERFORMANCE,SECURITY,STYLE|false|15 | |||
detectorURLProblems=URLProblems|true | |||
detectorIteratorIdioms=IteratorIdioms|true | |||
detectorMutableEnum=MutableEnum|true | |||
detectorFindNonShortCircuit=FindNonShortCircuit|true | |||
detectorSynchronizeAndNullCheckField=SynchronizeAndNullCheckField|true | |||
detectorVolatileUsage=VolatileUsage|true | |||
detectorFindNakedNotify=FindNakedNotify|true | |||
detectorFindUninitializedGet=FindUninitializedGet|true | |||
detectorFindUseOfNonSerializableValue=FindUseOfNonSerializableValue|true | |||
detectorFindJSR166LockMonitorenter=FindJSR166LockMonitorenter|true | |||
detectorQuestionableBooleanAssignment=QuestionableBooleanAssignment|true | |||
detectorSwitchFallthrough=SwitchFallthrough|true | |||
detectorFindLocalSelfAssignment2=FindLocalSelfAssignment2|true | |||
detectorConfusedInheritance=ConfusedInheritance|true | |||
detectorSynchronizationOnSharedBuiltinConstant=SynchronizationOnSharedBuiltinConstant|true | |||
detectorMutableStaticFields=MutableStaticFields|true | |||
detectorInvalidJUnitTest=InvalidJUnitTest|true | |||
detectorInfiniteLoop=InfiniteLoop|true | |||
detectorFindRunInvocations=FindRunInvocations|true | |||
detectorBadSyntaxForRegularExpression=BadSyntaxForRegularExpression|true | |||
detectorXMLFactoryBypass=XMLFactoryBypass|true | |||
detectorFindOpenStream=FindOpenStream|true | |||
detectorCheckExpectedWarnings=CheckExpectedWarnings|false | |||
detectorHugeSharedStringConstants=HugeSharedStringConstants|true | |||
detectorLostLoggerDueToWeakReference=LostLoggerDueToWeakReference|true | |||
detectorStringConcatenation=StringConcatenation|true | |||
detectorLoadOfKnownNullValue=LoadOfKnownNullValue|true | |||
detectorFinalizerNullsFields=FinalizerNullsFields|true | |||
detectorFindFieldSelfAssignment=FindFieldSelfAssignment|true | |||
detectorInefficientToArray=InefficientToArray|false | |||
detectorDontCatchIllegalMonitorStateException=DontCatchIllegalMonitorStateException|true | |||
detectorInconsistentAnnotations=InconsistentAnnotations|true | |||
detectorBadlyOverriddenAdapter=BadlyOverriddenAdapter|true | |||
detectorInstantiateStaticClass=InstantiateStaticClass|true | |||
detectorCheckRelaxingNullnessAnnotation=CheckRelaxingNullnessAnnotation|true | |||
detectorMethodReturnCheck=MethodReturnCheck|true | |||
detectorEqualsOperandShouldHaveClassCompatibleWithThis=EqualsOperandShouldHaveClassCompatibleWithThis|true | |||
detectorFindDoubleCheck=FindDoubleCheck|true | |||
detectorFindBadForLoop=FindBadForLoop|true | |||
detectorDefaultEncodingDetector=DefaultEncodingDetector|true | |||
detectorFindInconsistentSync2=FindInconsistentSync2|true | |||
detectorFindSpinLoop=FindSpinLoop|true | |||
detectorFindMaskedFields=FindMaskedFields|true | |||
detectorBooleanReturnNull=BooleanReturnNull|true | |||
detectorFindUnsyncGet=FindUnsyncGet|true | |||
detectorCrossSiteScripting=CrossSiteScripting|true | |||
detectorDroppedException=DroppedException|true | |||
detectorFindDeadLocalStores=FindDeadLocalStores|true | |||
detectorCheckImmutableAnnotation=CheckImmutableAnnotation|true | |||
detectorInfiniteRecursiveLoop=InfiniteRecursiveLoop|true | |||
detectorFindRefComparison=FindRefComparison|true | |||
detectorFindRoughConstants=FindRoughConstants|true | |||
detectorMutableLock=MutableLock|true | |||
detectorFindNullDeref=FindNullDeref|true | |||
detectorFindReturnRef=FindReturnRef|true | |||
detectorSynchronizeOnClassLiteralNotGetClass=SynchronizeOnClassLiteralNotGetClass|true | |||
detectorFindUselessControlFlow=FindUselessControlFlow|true | |||
detectorOverridingEqualsNotSymmetrical=OverridingEqualsNotSymmetrical|true | |||
detectorIDivResultCastToDouble=IDivResultCastToDouble|true | |||
detectorReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass=ReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass|true | |||
detectorFindSelfComparison=FindSelfComparison|true | |||
detectorFindFloatEquality=FindFloatEquality|true | |||
detectorFindComparatorProblems=FindComparatorProblems|true | |||
detectorRepeatedConditionals=RepeatedConditionals|true | |||
filter_settings_neg=NOISE| | |||
detectorInefficientMemberAccess=InefficientMemberAccess|false | |||
detectorFindUncalledPrivateMethods=FindUncalledPrivateMethods|true | |||
detectorNumberConstructor=NumberConstructor|true | |||
detectorDontAssertInstanceofInTests=DontAssertInstanceofInTests|true | |||
detectorFindFinalizeInvocations=FindFinalizeInvocations|true | |||
detectorFindNullDerefsInvolvingNonShortCircuitEvaluation=FindNullDerefsInvolvingNonShortCircuitEvaluation|true | |||
detectorDontIgnoreResultOfPutIfAbsent=DontIgnoreResultOfPutIfAbsent|true | |||
detectorFindUnconditionalWait=FindUnconditionalWait|true | |||
detectorFindTwoLockWait=FindTwoLockWait|true | |||
detectorFindSleepWithLockHeld=FindSleepWithLockHeld|true | |||
detectorFindUnreleasedLock=FindUnreleasedLock|true | |||
detectorInefficientIndexOf=InefficientIndexOf|false | |||
detectorDoInsideDoPrivileged=DoInsideDoPrivileged|true | |||
detectorFindEmptySynchronizedBlock=FindEmptySynchronizedBlock|true | |||
detectorOverridingMethodsMustInvokeSuperDetector=OverridingMethodsMustInvokeSuperDetector|true | |||
detectorWaitInLoop=WaitInLoop|true | |||
detectorIntCast2LongAsInstant=IntCast2LongAsInstant|true | |||
detectorBadUseOfReturnValue=BadUseOfReturnValue|true | |||
detectorFindSqlInjection=FindSqlInjection|true | |||
detectorUnreadFields=UnreadFields|true | |||
detectorSynchronizingOnContentsOfFieldToProtectField=SynchronizingOnContentsOfFieldToProtectField|true | |||
detectorFindUselessObjects=FindUselessObjects|true | |||
detectorBadAppletConstructor=BadAppletConstructor|false | |||
detectorInheritanceUnsafeGetResource=InheritanceUnsafeGetResource|true | |||
detectorSerializableIdiom=SerializableIdiom|true | |||
detectorNaming=Naming|true | |||
detectorNoteUnconditionalParamDerefs=NoteUnconditionalParamDerefs|true | |||
detectorFormatStringChecker=FormatStringChecker|true | |||
detectorSuspiciousThreadInterrupted=SuspiciousThreadInterrupted|true | |||
detectorEmptyZipFileEntry=EmptyZipFileEntry|false | |||
detectorFindCircularDependencies=FindCircularDependencies|false | |||
detectorPreferZeroLengthArrays=PreferZeroLengthArrays|true | |||
detectorAtomicityProblem=AtomicityProblem|true | |||
detectorRuntimeExceptionCapture=RuntimeExceptionCapture|true | |||
detectorInitializationChain=InitializationChain|true | |||
detectorInitializeNonnullFieldsInConstructor=InitializeNonnullFieldsInConstructor|true | |||
detectorOptionalReturnNull=OptionalReturnNull|true | |||
detectorStartInConstructor=StartInConstructor|true | |||
detectorFindUnsatisfiedObligation=FindUnsatisfiedObligation|true | |||
detectorRedundantConditions=RedundantConditions|true | |||
effort=default | |||
detectorRedundantInterfaces=RedundantInterfaces|true | |||
detectorDuplicateBranches=DuplicateBranches|true | |||
detectorCheckTypeQualifiers=CheckTypeQualifiers|true | |||
detectorComparatorIdiom=ComparatorIdiom|true | |||
detectorFindBadCast2=FindBadCast2|true | |||
detectorFindMismatchedWaitOrNotify=FindMismatchedWaitOrNotify|true | |||
excludefilter0=findBugs/FindBugsExcludeFilter.xml|true | |||
detectorBadResultSetAccess=BadResultSetAccess|true | |||
detectorIncompatMask=IncompatMask|true | |||
detectorCovariantArrayAssignment=CovariantArrayAssignment|false | |||
detectorDumbMethodInvocations=DumbMethodInvocations|true | |||
run_at_full_build=false | |||
detectorStaticCalendarDetector=StaticCalendarDetector|true | |||
detectorUncallableMethodOfAnonymousClass=UncallableMethodOfAnonymousClass|true | |||
detectorVarArgsProblems=VarArgsProblems|true | |||
detectorInefficientInitializationInsideLoop=InefficientInitializationInsideLoop|false | |||
detectorCloneIdiom=CloneIdiom|true | |||
detectorFindHEmismatch=FindHEmismatch|true | |||
detectorAppendingToAnObjectOutputStream=AppendingToAnObjectOutputStream|true | |||
detectorFindSelfComparison2=FindSelfComparison2|true | |||
detectorLazyInit=LazyInit|true | |||
detectorFindUnrelatedTypesInGenericContainer=FindUnrelatedTypesInGenericContainer|true | |||
detectorDontUseEnum=DontUseEnum|true | |||
detectorFindPuzzlers=FindPuzzlers|true | |||
detectorCallToUnsupportedMethod=CallToUnsupportedMethod|false | |||
detectorSuperfluousInstanceOf=SuperfluousInstanceOf|true | |||
detectorReadReturnShouldBeChecked=ReadReturnShouldBeChecked|true | |||
detector_threshold=2 | |||
detectorPublicSemaphores=PublicSemaphores|false | |||
detectorDumbMethods=DumbMethods|true |
@@ -0,0 +1,8 @@ | |||
<?xml version="1.0" encoding="UTF-8" ?> | |||
<FindBugsFilter> | |||
<!-- Silence warnings in classes generated by apt --> | |||
<Match> | |||
<Package name="org.eclipse.jgit.benchmarks.generated" /> | |||
<Bug pattern="DLS_DEAD_LOCAL_STORE" /> | |||
</Match> | |||
</FindBugsFilter> |
@@ -49,6 +49,26 @@ | |||
<build> | |||
<sourceDirectory>src/</sourceDirectory> | |||
<plugins> | |||
<plugin> | |||
<groupId>org.apache.maven.plugins</groupId> | |||
<artifactId>maven-enforcer-plugin</artifactId> | |||
<version>3.0.0-M3</version> | |||
<executions> | |||
<execution> | |||
<id>enforce-maven</id> | |||
<goals> | |||
<goal>enforce</goal> | |||
</goals> | |||
<configuration> | |||
<rules> | |||
<requireMavenVersion> | |||
<version>3.6.3</version> | |||
</requireMavenVersion> | |||
</rules> | |||
</configuration> | |||
</execution> | |||
</executions> | |||
</plugin> | |||
<plugin> | |||
<groupId>org.apache.maven.plugins</groupId> | |||
<artifactId>maven-compiler-plugin</artifactId> | |||
@@ -95,7 +115,7 @@ | |||
<plugin> | |||
<groupId>org.apache.maven.plugins</groupId> | |||
<artifactId>maven-shade-plugin</artifactId> | |||
<version>3.2.1</version> | |||
<version>3.2.4</version> | |||
<executions> | |||
<execution> | |||
<phase>package</phase> | |||
@@ -157,19 +177,19 @@ | |||
<plugin> | |||
<groupId>org.apache.maven.plugins</groupId> | |||
<artifactId>maven-site-plugin</artifactId> | |||
<version>3.8.2</version> | |||
<version>3.9.1</version> | |||
<dependencies> | |||
<dependency><!-- add support for ssh/scp --> | |||
<groupId>org.apache.maven.wagon</groupId> | |||
<artifactId>wagon-ssh</artifactId> | |||
<version>3.3.4</version> | |||
<version>3.4.2</version> | |||
</dependency> | |||
</dependencies> | |||
</plugin> | |||
<plugin> | |||
<groupId>org.apache.maven.plugins</groupId> | |||
<artifactId>maven-surefire-report-plugin</artifactId> | |||
<version>3.0.0-M3</version> | |||
<version>3.0.0-M5</version> | |||
</plugin> | |||
<plugin> | |||
<groupId>org.apache.maven.plugins</groupId> |
@@ -2,10 +2,15 @@ | |||
<classpath> | |||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/> | |||
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/> | |||
<classpathentry kind="src" path="tst"> | |||
<classpathentry kind="src" output="bin-tst" path="tst"> | |||
<attributes> | |||
<attribute name="test" value="true"/> | |||
</attributes> | |||
</classpathentry> | |||
<classpathentry kind="output" path="bin"/> | |||
<classpathentry kind="src" output="bin-tst" path="tst-rsrc"> | |||
<attributes> | |||
<attribute name="test" value="true"/> | |||
</attributes> | |||
</classpathentry> | |||
<classpathentry kind="output" path="bin-tst"/> | |||
</classpath> |
@@ -1,2 +1,3 @@ | |||
/bin | |||
/bin-tst | |||
/target |
@@ -51,8 +51,8 @@ org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTags=error | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=no_tag | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTags=ignore | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private |
@@ -1,3 +1,8 @@ | |||
load( | |||
"@com_googlesource_gerrit_bazlets//tools:genrule2.bzl", | |||
"genrule2", | |||
) | |||
load("@rules_java//java:defs.bzl", "java_import") | |||
load( | |||
"@com_googlesource_gerrit_bazlets//tools:junit.bzl", | |||
"junit_tests", | |||
@@ -8,7 +13,23 @@ junit_tests( | |||
srcs = glob(["tst/**/*.java"]), | |||
tags = ["bc"], | |||
deps = [ | |||
"//lib:bcpg", | |||
"//lib:bcprov", | |||
"//lib:junit", | |||
"//org.eclipse.jgit:jgit", | |||
"//org.eclipse.jgit.gpg.bc:gpg-bc", | |||
"//org.eclipse.jgit.gpg.bc.test:tst_rsrc", | |||
], | |||
) | |||
java_import( | |||
name = "tst_rsrc", | |||
jars = [":tst_rsrc_jar"], | |||
) | |||
genrule2( | |||
name = "tst_rsrc_jar", | |||
srcs = glob(["tst-rsrc/**"]), | |||
outs = ["tst_rsrc.jar"], | |||
cmd = "o=$$PWD/$@ && tar cf - $(SRCS) | tar -C $$TMP --strip-components=2 -xf - && cd $$TMP && zip -qr $$o .", | |||
) |
@@ -7,8 +7,18 @@ Bundle-Version: 6.0.0.qualifier | |||
Bundle-Vendor: %Bundle-Vendor | |||
Bundle-Localization: plugin | |||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8 | |||
Import-Package: org.eclipse.jgit.gpg.bc.internal;version="[6.0.0,6.1.0)", | |||
org.junit;version="[4.13,5.0.0)" | |||
Export-Package: org.eclipse.jgit.gpg.bc.internal;x-internal:=true | |||
Import-Package: org.bouncycastle.jce.provider;version="[1.65.0,2.0.0)", | |||
org.bouncycastle.openpgp;version="[1.65.0,2.0.0)", | |||
org.bouncycastle.openpgp.operator;version="[1.65.0,2.0.0)", | |||
org.bouncycastle.openpgp.operator.jcajce;version="[1.65.0,2.0.0)", | |||
org.bouncycastle.util.encoders;version="[1.65.0,2.0.0)", | |||
org.eclipse.jgit.gpg.bc.internal;version="[6.0.0,6.1.0)", | |||
org.eclipse.jgit.gpg.bc.internal.keys;version="[6.0.0,6.1.0)", | |||
org.eclipse.jgit.util.sha1;version="[6.0.0,6.1.0)", | |||
org.junit;version="[4.13,5.0.0)", | |||
org.junit.runner;version="[4.13,5.0.0)", | |||
org.junit.runners;version="[4.13,5.0.0)" | |||
Export-Package: org.eclipse.jgit.gpg.bc.internal;x-internal:=true, | |||
org.eclipse.jgit.gpg.bc.internal.keys;x-internal:=true | |||
Require-Bundle: org.hamcrest.core;bundle-version="[1.1.0,2.0.0)", | |||
org.hamcrest.library;bundle-version="[1.1.0,2.0.0)" |
@@ -1,5 +1,5 @@ | |||
source.. = tst/ | |||
output.. = bin/ | |||
output.. = bin-tst/ | |||
bin.includes = META-INF/,\ | |||
.,\ | |||
plugin.properties |
@@ -85,6 +85,12 @@ | |||
<sourceDirectory>src/</sourceDirectory> | |||
<testSourceDirectory>tst/</testSourceDirectory> | |||
<testResources> | |||
<testResource> | |||
<directory>tst-rsrc/</directory> | |||
</testResource> | |||
</testResources> | |||
<plugins> | |||
<plugin> | |||
<groupId>org.apache.maven.plugins</groupId> |
@@ -0,0 +1,41 @@ | |||
-----BEGIN PGP PUBLIC KEY BLOCK----- | |||
mQGNBGAHBLQBDACsS1vFqE3qgKD2R5X9n90Gz8bucwwvJWIqaHDsVoAtF6IcKIDo | |||
1hQC9YksTQYl/L7BsMDdmjyEbWRfzW4ory5596d342Hl6g7ZB5jJR5kJJdhy2MCJ | |||
BUiMy/724Fr/Dz8PNPcEoULz9ZH7HEaPRKqWWEQDUCq5ak0MfLKXtWVUBgsY5Mry | |||
29d/GLJvnxZ5v16PK+P4oqZ7vh7FWJPlqPK2TCZ6s1rYfWlu9XbHOzwXwozVg7IX | |||
tfFq4Rij4c0sg0S0GY8hGAlnOpRc/6J2S41Y8p3WqND6r1LPDQUFnNCKXVoHUGeK | |||
X9U5iAP7pxZSuonsFCqr3CDGxr+kKUpbfZeLrqTA4lBUK7T6w6Wq0qHosCYUU7YC | |||
GZjlEeCZBRWNfeq45LKlhdNUxHWWgaBsgWaaDmpFWaivblmQGOvmSv1nJMNmedRs | |||
DSF51nsJnkQceprsvThSa6qJwEYi7pj6L9HO2UGgJLCb3dL5VTQih2gdhghckUSB | |||
okUkvqBvvdiP2nEAEQEAAbQdVGVzdGVyMiA8dGVzdGVyMkBleGFtcGxlLm9yZz6J | |||
Ac4EEwEKADgWIQRPCAvglun3I2Bs1iITM2XBzCpwbgUCYAcEtAIbAwULCQgHAwUV | |||
CgkICwUWAgMBAAIeAQIXgAAKCRATM2XBzCpwboiBC/493+ruANV2eiro8MY8wZ3Y | |||
gdjp3pHBSg9RK74SIh95J+MW5qzPwkU+vHd8l0+aj9e1sDQb5BFcFk/Z1ioI3TDW | |||
B4vYWoMkdN932fJ/LcIlhOGjWwSNFZphbYmJzrAwUTA499yx3jt9Dg+vSU88S+8S | |||
FzYe6CBNt+PqDCbk6Gm+ZcVpR+elq/QJeyhdDzCCrrfNXwPwsVGAM61Z8SvdvNKE | |||
DA5gHXRsOKf8fu8lqW2Ay0MCvgsZLMIGOMDPCyBUd1bhlU0p18V6D6wdatfzu9gR | |||
X/k36HJyqB2cHh89/F2KdBSonRVRJOvHc/88zEeRFkiV5pUyrXv40l099+5dvA+2 | |||
h4ODftY7ZbR22k4iX5rqj2BRow3H+N5lTIWgiADPUl+H8z4ZY5G+LWk9Xms3o1G9 | |||
DmEepM3ma1pg4sZbxf0iStikch7aPvL/HgGRPJnDxA/W4KJxqmSw9TTMH/6XHq3D | |||
ah5Z1lbcylChgrFLFVJi+shnLTZSYttTeKOIqTPi0765AY0EYAcEtAEMANS23tqF | |||
Dr69wz0AaT7tjoccT/WlSO/gxd80ShMr4vbr21PZp8qGklFmlcrSrMDRwfXY04x2 | |||
qxHR/Kf+hCD5gNvg8kh/yH2lQRcvekzQ4/rLmSXBfGOFg+LioQQ3CZJ1MZyIHzu5 | |||
YVZ2pqALfJwJSw9P5Z340y8sq8AOPaJ+cpIC0rYBp9BUAmz9IeLVT7fUc6CjaWBo | |||
++E8H+9FyZC71RIPNcCvY+24Qky8ms7nw4hA47Dlht1pqL8dzOggCnohuSYMCXs2 | |||
YPLvDGdZMg7GgQ3AyZawDmjTxFWt51VU5hunGfGiC5Aock8rVHSYsQzUFjVBSR+Y | |||
Zy+c4noxZD1eRfb8KdFnrewyVqGKFtc/JwA61qhhyYFe5AWMAFtudjGYG0WiTP82 | |||
CmFFc1Qsvyls9G2yMkLuay5wsdIJMnRW9XwBzwxm0mdZI6D3nSbWjPUUfRcGBY8C | |||
Hqpc736G+UzMevZtorwy/5Q6D8v+Obrk02DIDKa6CJ7g7dTwK0I/fleJlwARAQAB | |||
iQG2BBgBCgAgFiEETwgL4Jbp9yNgbNYiEzNlwcwqcG4FAmAHBLQCGwwACgkQEzNl | |||
wcwqcG6mYQv8CFIVGj7/Qnr+wmviMzm8+B4WwQIUHGryqv9hnfp9hLOXMFmNuEDl | |||
QYkHVChWO7ehrR3fpvpebhcieV19skf/WO8xm0pGSXyjV2/0/bVhXq01xesXHH9r | |||
4aFxsCu0E8M9fZVAHP7NBr4A67knQ4EHRF6Rwml2ba6Zt2oP15IHvsAq/2B3f8ar | |||
5sUau4zM1cItG3tg49rbYr6V71HdgkWA22+EkbXL/Qq3haY/er2dIGc73lu8t7oQ | |||
msGK4LSAGc2661wMvJ6w6feCagkXAtrqyxodhSLoWgF3i0QVQnMbgmYWKEK2B6YA | |||
g669CZCCXJF+9Ebq+PP/d3Cy/k9iUmWDh72C7iL136kYZt+71b+yOmlDRT9l6DvU | |||
FP3bhRZWomOt3F3aP5mAdbwrP1NbvlxTYUAf++nUPdpr0Jrvgi67/VHVjaUtVh/K | |||
gVQ2C+4Cp/fllxXXKQMPcC8dD1x/AL6ytDzPu099ETMULntgbt7A5Lsd/fFScnF3 | |||
ZNx6wjRReIvT | |||
=8E/K | |||
-----END PGP PUBLIC KEY BLOCK----- |
@@ -0,0 +1,42 @@ | |||
Key: (private-key (rsa (n #00AC4B5BC5A84DEA80A0F64795FD9FDD06CFC6EE730C | |||
2F25622A6870EC56802D17A21C2880E8D61402F5892C4D0625FCBEC1B0C0DD9A3C846D | |||
645FCD6E28AF2E79F7A777E361E5EA0ED90798C947990925D872D8C08905488CCBFEF6 | |||
E05AFF0F3F0F34F704A142F3F591FB1C468F44AA96584403502AB96A4D0C7CB297B565 | |||
54060B18E4CAF2DBD77F18B26F9F1679BF5E8F2BE3F8A2A67BBE1EC55893E5A8F2B64C | |||
267AB35AD87D696EF576C73B3C17C28CD583B217B5F16AE118A3E1CD2C8344B4198F21 | |||
1809673A945CFFA2764B8D58F29DD6A8D0FAAF52CF0D05059CD08A5D5A0750678A5FD5 | |||
398803FBA71652BA89EC142AABDC20C6C6BFA4294A5B7D978BAEA4C0E250542BB4FAC3 | |||
A5AAD2A1E8B0261453B6021998E511E09905158D7DEAB8E4B2A585D354C4759681A06C | |||
81669A0E6A4559A8AF6E599018EBE64AFD6724C36679D46C0D2179D67B099E441C7A9A | |||
ECBD38526BAA89C04622EE98FA2FD1CED941A024B09BDDD2F955342287681D86085C91 | |||
4481A24524BEA06FBDD88FDA71#)(e #010001#)(d | |||
#208024A31FF0F6B3E5E91F2ED58572F1A67F1D9AD9290991BF732D1DFFE134E058E5 | |||
9BE459478CC5D42058997CF7EC79E55A9CBF10A9AAC761E04A85A5AA0A07DAE61DD0E8 | |||
36311534EE606D5392B42D8DEB7824B594280FDB2950D39886B58F0D24CE15F2FF88BA | |||
819B8F4566202B57A9F5C6743862FA80E7429C83CEA57B189ABE4AE657B28DAF7D6EA7 | |||
6CA89635B9B6232EE14779452D636B919E707B92B13DA3229133A953DAF021E0928B83 | |||
75EDEE98163C2189E22CE9A236C3D0EABD2608DAEF09211B2C77FFE9158A95F8EF2750 | |||
221C5ADEDAED0446DC0E4CD8D38AD897D44FA3915934B6CF03F489DFAA6D939AB9F8EF | |||
1C2A0CDCFC3F2207D63A9EB55B09A0A45323D5F59AE4A9D48E819E98E53D04F022905A | |||
9C4D137F32CB33A974F202B0D3AD4AC64CFBA2A4C18650B671AB753D1D3BD7C4FCC8D2 | |||
0F85D1876D89A3D94C77423778C08BDF8FBA23A8501D766FC1B4D51F2D4BB4C27B8491 | |||
CC2595FF54034F4F192D668C1934D292752A4E44C95135D29449B75928BAF1A2389ED9 | |||
#)(p #00CCD74AC0DC1CC46052F784DB19545A09FF904846247BAD1AFA5E00CE84A4DA | |||
BFCD3BCA175508C068553226DBA49EDAFBCC33CF2A914F9006326FCB62C0473B1E93F6 | |||
DCF89A24006B090834E5686464A8C216B70AD797732E671ED78CD3E922161069E46BA7 | |||
489F2A79CE46BDC4E6F5FCE97C3C9DC59399212235C53246609F8B7FDBF2AD191B3FB4 | |||
4CC59760BA6D2029541811D1E9B72DC2ADC98513589A9715C82EE88ADF9000A41512C9 | |||
6D491D2A30269FBFCD9CF5D2F275A1DBFFEEB72BE5#)(q | |||
#00D7532ABA5F442A994ED8290AA71EAAB6F8137FE3F01488298637084157972D31EA | |||
E9F495B4360A6E92ABA7C2418A5960DF29B8C8146CC7D1DF1201C17509D7315B6ECF86 | |||
F0A73C9F5B48D96F2108DD323FAE6BF897A8CB773EDCF5C82E203813945405F414E3F2 | |||
99EEDE43EE6259FDED1C01B47C20F67AC488209585FE6FB7D958AF5EF567737E1ACCB4 | |||
E293724BE8AB6159CD5A6603FFEFC2DBC30FB6EAF647DBE7D9664ED0BBA1C4A2268AE3 | |||
DE0850572E145BA811EB2087E1E26490F9439D#)(u | |||
#00A8026DB6685170EC05DA3574451678718501489843227BCEB772FDB86B20AB2F2A | |||
00B790A2373FD5DF7AD2CAA2E3896C4C9CBA08581F3644DF67743FA4BE1153421F8FB2 | |||
47F0EFB64C566CB7E361BAB21CCAF029B43B7172232D11D14912BC035E17FB8BC663CA | |||
6766E6D2531F87AF378896A2AC7D98824AA8F51C5D6C11B7DC266209BCD3B23597F02B | |||
A317CCAACC979402F84E47F3D143187F9739FE9A6C71829CC94E4003D70CFFA33AC0FA | |||
A12776EFAFFB8261ABE664C6A6FAE623D3678F#))) | |||
Created: 20210119T161132 |
@@ -0,0 +1,42 @@ | |||
-----BEGIN PGP PUBLIC KEY BLOCK----- | |||
mQGNBGAHBAUBDADAzIW1FhCcQmP/NDhzXoeRQ+DNACqTed7eEhqm3rowkW4wKi56 | |||
v1UxFR0ZoA3LoT1oQQjiL3IS2l4/qpBR3JQhMFH3pl7yBsCIrN7JvZfAvxq2Ud4e | |||
YbdonY8mv/yCLq+nkTWlHkWGCppKMm6DupEUw5CFUCiptPXIxikU0uQYB7VRtXhh | |||
Q1RGdsv6mcOwMIh7hj9flTrX025x0vRypjqgDR05RuM3hMMpJDGMAuf51w/lbRl9 | |||
dAsJzFzg2cf+8qv92Gx3RuP3a3yl6pEuKnkpddC47lj2pvuhWZBf2sXZMyPvMIvA | |||
Dve4GIVj6k+wXE3DMp0xMy0Fvaxw5OORPxUAKNBR/BgjoRbkbjm+rJZzviu/XVPR | |||
42+78isvsa+lnEAmTeoTqiz9nlTTPN+6JjwJXYn2LuiFM8XzNPJwNmd/86lW1qbP | |||
DxZHhD9jjeXZNHUBUCKTIj2Rs2uFa0xrALdhhhGEao9JlVcUqz/Tw85qC+DlzSa3 | |||
3re5C6wGe2pnW3sAEQEAAbRGVGVzdGVyICjDhG5kZXJ1bmdlbiBpbiBHUEcga8O2 | |||
bm5lbiBGb2xnZW4gaGFiZW4hKSA8dGVzdGVyQGV4YW1wbGUub3JnPokBzgQTAQoA | |||
OBYhBPidC43dWzUvOqsRbzx6+72u3pDoBQJgBwQFAhsDBQsJCAcDBRUKCQgLBRYC | |||
AwEAAh4BAheAAAoJEDx6+72u3pDogeAL/iNn1aKxA7pKmucyuzcbzvUjtcbbqFL8 | |||
lOLdRxkrQNCDMb+wHgGY1UqJ6wsDruhV+TdPLzUXHpCl731MeLxZyIENr4wnjTBf | |||
Cr4SU8eFUkusVf3aWK3rlk54W50EkfBjMvDVavRKNkVbCWAAwXZ7mTRf3UlWxg+F | |||
9Sq4j2P/hEZIznKV3y7zXLDYg0OpMZLgbo3si0CD19/1T/8Z9C680qSwyAiPtjRo | |||
vfJYqZFQc+ZH7j1Hmvg68d2Qwrkg/WMfOGoTLZq/6PQcM5leQBAodcS1t7C8o1JQ | |||
6D+f2gLHpMfFdUKh9TkmvnKYI20TWUVm9XQLqyAHsOn2vRMUhydcZ8OP103TKmP4 | |||
mbpgiyp4i4S/7XofHSeFBrbdqt73ebubESuZVXNjTuuSUjH8Jq5nHq22ZrmotSd0 | |||
FNwc9qQmwPG/gmOGq3rUdT/KzUVntc66QN/+hMhFDYMRJsjJhhyszvGuuBp6vBzI | |||
lMZqx5jqOaI9ON0m0o8CKC50sKdJ25G3wLkBjQRgBwQFAQwAqMXwgfSaIM+eSQWs | |||
xb4Sf2aCr/RZi5wzZz89lSomMcblqtCpuHv9/C1PSd+N/D1yJKzPChbDjHh6B6gc | |||
4OUvuDKHmxK+oAiURpvR+yJEdbSEYwiBhfAUD6u2q3IfY5PpyyZT3NjZ9EY8FpOX | |||
wpgdSOdSiZVZfZt0xUPsGbW/xP1yVR1NHYLuZX5P5oTCvyNJyP8zQQmToamJsvzR | |||
v9r+2sa5di9roe53kZwq24VjIvTDOOE4xoYEXk5UD83u1LA++9Nfdisxxts1bxgj | |||
w1ThO/IRTyY5y5bKSQPskYFD3eVz+azjVurqhbj6ep69mR2X2gjLCetz6G1l4PU2 | |||
R6wcqVG6gR6XpGFPsN+M2JRtbgKrtMElA8egJIMMpH4hdXYqIqmpe/31zXhClHkO | |||
99EbBpk6OawZC+MnreQFN4NIK5uO2aLi+3KL1FFnNlFCXkh/8afbt4+6rHcWC6En | |||
q5W0ZkLZnpjdFOF5NPTinAdei+14gnf2QhIlFLeMHvqiVEXvABEBAAGJAbYEGAEK | |||
ACAWIQT4nQuN3Vs1LzqrEW88evu9rt6Q6AUCYAcEBQIbDAAKCRA8evu9rt6Q6DcX | |||
C/4orMX1YBZNJK5hLLdjfk45EsQDfCnhf8H991xd0Rq4VPJP6bvzikSdOn9bTUEz | |||
AAhA4JnFu9AMTh8ioOA7ZtViIccplFBivsxi3rAVrQvmCfoP2AdHfG/jB6D9uWGs | |||
MV5/o1p93Hr0ReO73HK6G4Q3FbJOG3fg6wTcMYyyEQrD5g4IQhKiIhudUlSkKUkA | |||
9hWKlXSLw3Yx/S2Nq5Ye+Pqr4CU7UFOTCsBIH4Ky+6gLTmP6esPx0k8vXLcOjaCk | |||
ENcLi0OaL/AgfATH/InN3wzrx2AFfU6eQdEG7HS+402eHl0fmWwMGV+SCsLl+2hx | |||
AguLFwjetuVrroc/d+XeZdTcpr/2vojsr4UgbkH8Pa2KrGIpK7V85nSOeVbpDUru | |||
tuimIRSxIQ6GDF2c7Ih5yBy+JPV47gppSV/GgHPgrOlebeqy4sytshRiEYw/nJzZ | |||
LKBaG6gykN+6MeV6+A7c1BlCYpyi2vcyvouU+l3/Z9gR7vY+Oj1eAaxrqeTFf++3 | |||
qnA= | |||
=03Wd | |||
-----END PGP PUBLIC KEY BLOCK----- |
@@ -0,0 +1,45 @@ | |||
Key: (protected-private-key (rsa (n #00C0CC85B516109C4263FF3438735E8791 | |||
43E0CD002A9379DEDE121AA6DEBA30916E302A2E7ABF5531151D19A00DCBA13D684108 | |||
E22F7212DA5E3FAA9051DC94213051F7A65EF206C088ACDEC9BD97C0BF1AB651DE1E61 | |||
B7689D8F26BFFC822EAFA79135A51E45860A9A4A326E83BA9114C390855028A9B4F5C8 | |||
C62914D2E41807B551B5786143544676CBFA99C3B030887B863F5F953AD7D36E71D2F4 | |||
72A63AA00D1D3946E33784C32924318C02E7F9D70FE56D197D740B09CC5CE0D9C7FEF2 | |||
ABFDD86C7746E3F76B7CA5EA912E2A792975D0B8EE58F6A6FBA159905FDAC5D93323EF | |||
308BC00EF7B8188563EA4FB05C4DC3329D31332D05BDAC70E4E3913F150028D051FC18 | |||
23A116E46E39BEAC9673BE2BBF5D53D1E36FBBF22B2FB1AFA59C40264DEA13AA2CFD9E | |||
54D33CDFBA263C095D89F62EE88533C5F334F27036677FF3A956D6A6CF0F1647843F63 | |||
8DE5D9347501502293223D91B36B856B4C6B00B7618611846A8F49955714AB3FD3C3CE | |||
6A0BE0E5CD26B7DEB7B90BAC067B6A675B7B#)(e #010001#)(protected | |||
openpgp-s2k3-ocb-aes ((sha1 #84A4A8051974D94E# | |||
"26420224")#914E6847983627126D1CDF93#)#DEB66FA3201F91591F688F5D2B1B79 | |||
39FD75F58A962C227BC739C6F2F814ADE5115BD85B2E55427153CEC82612F0C5BBE8B9 | |||
71A0E5A6B796111B6B1A03C4C926825F03B871CBFE0F64BD0F0CC65EA34E718BA823BD | |||
136D78C9E88CA1733DFC8D6A38830274322A589BC522A2A824FCEAF453523CDD9BC391 | |||
EEF1355470C110E9A92681DA0C61563465D5238CECCA2D6CFA78FFDFBDDA17A308D6E1 | |||
3B1858890EF25A7655F22FE6305AA0129DE5A353B657065E608A616A23C6AF561C4472 | |||
5AA705E55343E9C728641BB63C64F804F76A4C5008CE5FFBC09F03B632B42180425D28 | |||
9DC1201D91B1989627EE5930E6EF2F6606108B2F048934A9D79DB4834DD950C4A2013C | |||
A40B50EE54FA9E3CCB20C210244BFACA795494A1FCFF35856ACF63214A0498ED894BAB | |||
FE80CC24D8A478AD08D0BE8CDC8F357FB7F28A30B87540B9B4970D6EA0AEEF46A2549F | |||
BA43A98FAD75B4228108DB50D1C3654422E24B4C7754673A66281BB283CD6A1EA8E64B | |||
97DC9083C62034BF7FBCD193830F8FEC3589673B864E50EF7AF4DEE046BD26041E2925 | |||
170EB7B6DC6060E78309CC8A136AE9CE44D3B4EBDEE4479482464D0D23C13529184021 | |||
795557323D353A70CC710EC2A79C66E860095C082E40724867A9ABBFD3407B2F92CB2A | |||
D0D95CC8DAC2FB2C0187B3BB09AEDF869AF1969BA641027D4D5DAA31B1DA5822D40A5A | |||
7FAD1C054C02CF8F8F692B1C45C879299C0E9D5E5A165F6C22DEEBB8C16FFB91F381C4 | |||
8FCB209657A7BF9268BD34808D0A9D3D6F50F7026BC297FD3A08790B8EB5CC0291246B | |||
16E4B50A7E9630B33F59B5EA24EEA396F07AEFD0C262BE9915CB32D5F03673CBD3D20A | |||
831FDF55B5BA3D03440A8E1A331147A8AE0760EC593EDC881F5F0A04F4FCBC80C1531A | |||
4DE71D014E3612C2C679BEF3AED59F358ABA5731FF80FA15EAD2CB95AC548F6AB0FD7B | |||
BBBB2CB63DFFE9E672605B7F54EEA4B4C046C4CC8036F2F76260CF068D232A40F492FF | |||
9648CC7459F0F46FEABA3D62B9F421B0F8A1BF914E41702540213848345498AA13F989 | |||
49EFC2888D3720DC34D20634472FC3A194F1403C1609C38A020F7E47F3205CA5C0CB50 | |||
26270083ED153BF97375407514BA15D92808A8C10F8160880F6981BE53294292E4EEE8 | |||
D215E7854FC79016B64984BBBAD2E99EED8D66B25575183C279E4DAFCB63F1067FA2B0 | |||
0888A9C226D4846376520720FC1E947A93A1D32444F78B2F4EB836A6F8C685C1D82958 | |||
E31560C3FC861D2B68B889E1B5EE0476B914DFE316411F750D252F24076E53557AD5F0 | |||
4050E5E839B33E5B8AF16FDD9FE033B39796A52DE8AF65375966D4DB137C85C800B5B9 | |||
0E29434E4B215DE35E60C85391DFDFA572C6F9747A0EA0964236FBB3B04394D9DF0694 | |||
6E4CC9CBCE352382908148D265293C6EADE7C2AED6F5AE#)(protected-at | |||
"20210119T160858"))) | |||
Created: 20210119T160837 |
@@ -0,0 +1,30 @@ | |||
-----BEGIN PGP PUBLIC KEY BLOCK----- | |||
mQENBF7EL8wBCADO46xh7nXn7vZ5ow2Zdrp7WTh9BlT2wtaHNKpnKvSoYHjJbbGz | |||
yF8Jf/qVPuXNbjx2df1lT7zT7x3evcjQoNy80deftCw8ApZB9RMOo3uUIqS2VpO+ | |||
cS9rjTgBRFL6xDv3g4++CE9s+5dKE9gKkwleZ5/tVqUIoHPAIUEjpcPHngi5m2bi | |||
tSmQUYWLGcliR1E79sJMSzPt1neksqHFMJ1KTEJLAABZ0t3PiBzmycIQWThX3uU/ | |||
lcgnZmmhWCJIqV0yRZqxl61ejUfq+zK0T7MzhAAugqe7D6BM1FRwZRNCHwDQXIvt | |||
/t3fczTe+x9oTy4qX4MfaP8lHM0223MwGR13ABEBAAG0H0EgVSBUaG9yIDxhLnUu | |||
dGhvckBleGFtcGxlLm9yZz6JAU4EEwEKADgWIQQILQAv4wNQfEJ6I/NEWemKCmiQ | |||
+wUCXsQvzAIbAwULCQgHAwUVCgkICwUWAgMBAAIeAQIXgAAKCRBEWemKCmiQ+xev | |||
CACZSWh4xjTgafwGMP9RnReOhubVmfHS+XGlidDQzJDtshDQddPZ3oQwyTe3OgkW | |||
ZgOrzjrHGsZp3WZmGUZejrKt2Brqp+h+VRujFVcKk4N9A52BkM6OeT9lzBabOpuA | |||
UaDsNMSFsMcGTTYpB16+sDcyui8LW1jGi1y+8aQa+u1lIk/vVycq8o4htn2Af8xZ | |||
rAT8peapsjoNjETEs8OQ0al3Q0UX9amW6Rq1zZZ0XtoXDCPTI01EfczDMN+AZoFk | |||
UYHwSREDFLSh+c+q1HhYp4TqP+2a5Rayna//n7zci1PmSX7zD3iWzV1jEQ3Jm8U3 | |||
DY+P/WLezQdSJIBVCFpCualquQENBF7EL8wBCAC+ef+vNvfu1jl9BXpu6K9PG0I5 | |||
DQfrNtcdPq90O32ipvsYvqGOJX9MHoTyxBPLew+e5UsYb3ex62JyJqdAaqSwYXEN | |||
MBESZx7yBqBMUvildfh8dowbJeblxCf5KsE4C9uNfg4ApWGD7PjVsUCh47V8VcfG | |||
ymCxxq80r+4GfFtt/HC+l9fPUnDLuXpAWEM2GPUzcauUoEXxZK6nhstYCRlKlQcK | |||
Tn+LtCC7SGpYlqvwWBzAnOYP9+eZfSJ897g0AiTEhK0JsBlDAb3UAWHYHkAkVa1+ | |||
oU/UedhPC4j2Q7RzPQFMun6aGkaDrntCxvT7IFiMplPG7iy0JDd6ubrWSzivABEB | |||
AAGJATYEGAEKACAWIQQILQAv4wNQfEJ6I/NEWemKCmiQ+wUCXsQvzAIbDAAKCRBE | |||
WemKCmiQ+xoBB/9BAmlHQUmVl/bkwszAcyXkR5HsyA4htMJt+6GKlqftuhLP0SGK | |||
Il+7GeK6NqNdQXxXG5Wj6dn7ZqWalQRA0evEa6VLH+74zrn0llWfzTPIcP1bHW7l | |||
uYaOzZ1z/q4FoEGNJxp/jdToZ4970OXLzqY/G/QlMJIlXWCC0EXNYbKCEpOE9uvW | |||
h4kWe5xeGOmhZylYbzurTDzqEtKy+LZ9f2xNYn6ElcWtwxsxwSY7L9B3eNcCYE46 | |||
Np6uqzPffB9s7PHW46yEL1lQs6ME+9hBGyjeVop+Wg9qkh3YCrp+KY5Vkmdndwkn | |||
Th4FnTpcCiS06fCVHHC5kelh+H6TgRA+XQ/V | |||
=WGUq | |||
-----END PGP PUBLIC KEY BLOCK----- |
@@ -0,0 +1,30 @@ | |||
-----BEGIN PGP PUBLIC KEY BLOCK----- | |||
mQENBGAHViwBCADaE67a5U8oqNjqg//SmlNLd+MrFIccHsg+pkSDhF6ipjZEXdFu | |||
oRQ116tH/qY1SzsHw/TMLujYeTBW0KwQ5E2+TWagckL8pJpDt7ZC08CTK6u0Xvjy | |||
BSqy/t29NPTuQxxxqRx5gq91mtuo8v00fQmqkbFUgkVfEOKOv4qe2tlaR5pTvpmV | |||
VboXOls87RPgP/X665kamHjsywrsDpZ5FbvPS8E2kKdYXqeHaiEU4i0Bizjx3diK | |||
ilPEIxxl8zgDsROXyKagCy0KOOajBqhFhStQH1soIzvk8aG/9eItKmTa2v1BD2mV | |||
UlZNQ9ZfsnXx3QIBLmA3jugH69rkcekHRCWPABEBAAG0HVRlc3RlcjQgPHRlc3Rl | |||
cjRAZXhhbXBsZS5vcmc+iQFOBBMBCgA4FiEEJJx/+EvV3v35cJ+Jx5r/T8UxegwF | |||
AmAHViwCGwMFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AACgkQx5r/T8Uxegx+hQgA | |||
pIy+E58aWh9FIHW/POqLB/y3v/GbYdIbzk9ro0FAXQ2tQsoGQbsuLckJVIlyja7n | |||
oQX23OmWTOc2tj/Kpy9lZ2ButW43FaMSiLh1G/VtM5pqJ9yHFdb1z7Q+LHMhhB7w | |||
RKOoNSEgkwtxv4LAkKz5t/BrDU0hvDPxWPqCSRvSAEE6qIY0fa3mLf9ijy3gLlCd | |||
hBayUC53W0tL7gLHgprTM/fX7b1pDab8beorroPHs5XzcYUBleaCGmEYbtV2eXPQ | |||
5irOXOC/D/E8vOfOZwhOhFOZk9b4UnhFK4OCfpKIzIooc6tlboVPhdx7jb5DkXQo | |||
rozfavEvAPx8INi9KNmdBrkBDQRgB1YsAQgA6q/O6mAPB0kj3pnpuIdJC8EumgKm | |||
7W8rv+ZfRGePg+IEEm5DeFKtfWl70YH33nGmwnWB95wO5412JCNC174z5LKDBbz5 | |||
PWT/yTNnmjooxj6G4p/YVwXYJkvfaDP+kQnYgJAybpeqTa30tES0eqvI0J9aQo1h | |||
GSMRCkE6QMV45IMj6gH9rptQv9e8U6gbnwBPxWPG2FH5rsGIGQGzIEmGxmKRyxXm | |||
YDU4f7oWPHSg1ikQqCzAxxCBxeCOaid3acLK8//TOwF/Do8GPJbcupEDqsgbFNGM | |||
BDWtmkmxjrLntlU+dvIPcsBxdBUrrADiJ/k7EfFv3kHfLfdAonSdKZL85wARAQAB | |||
iQE2BBgBCgAgFiEEJJx/+EvV3v35cJ+Jx5r/T8UxegwFAmAHViwCGwwACgkQx5r/ | |||
T8Uxegy4+AgA1bzFKpsqkwrjZKDCCT759xeuUbxnYE9kBJgFSVuhn7fUbB4MoHx4 | |||
shBptx7iBOdxxT7yC0oaDPhbiIkttb/c5W0f6JuLr08JpjkFfkrWF+dMcVrtXwPx | |||
i/30ccV98qWJDCBunyeCwBNie1Ck+qXMxm3FYy4qIbftMQ7mG6KKN6eFlbxu8B6M | |||
p93DFUvycGH9CWz0yJcho7KT0NSSyoLZhJz2uxRe1BwGMV20O9AG9yicsU0/uJxY | |||
a2Hble8NkH54XDuZkrsBaAb/o8UsWP7AJdYYsb904UZDIZNRfjWapOmODnlnK8Ta | |||
Q8pyYRGS5of1SapatMfpQZF6hdsamnTH6A== | |||
=guSE | |||
-----END PGP PUBLIC KEY BLOCK----- |
@@ -0,0 +1,32 @@ | |||
Key: (protected-private-key (rsa (n #00DA13AEDAE54F28A8D8EA83FFD29A534B | |||
77E32B14871C1EC83EA64483845EA2A636445DD16EA11435D7AB47FEA6354B3B07C3F4 | |||
CC2EE8D8793056D0AC10E44DBE4D66A07242FCA49A43B7B642D3C0932BABB45EF8F205 | |||
2AB2FEDDBD34F4EE431C71A91C7982AF759ADBA8F2FD347D09AA91B15482455F10E28E | |||
BF8A9EDAD95A479A53BE999555BA173A5B3CED13E03FF5FAEB991A9878ECCB0AEC0E96 | |||
7915BBCF4BC13690A7585EA7876A2114E22D018B38F1DDD88A8A53C4231C65F33803B1 | |||
1397C8A6A00B2D0A38E6A306A845852B501F5B28233BE4F1A1BFF5E22D2A64DADAFD41 | |||
0F699552564D43D65FB275F1DD02012E60378EE807EBDAE471E90744258F#)(e | |||
#010001#)(protected openpgp-s2k3-ocb-aes ((sha1 #3E87FF0806B054D6# | |||
"26420224")#7E8282B83522F91C53D76805#)#70B1997D514FF5800155A90FD785E7 | |||
7DC2D782C48FC9BE44D0192C0AD56804468C910A202191EAD077B5542C95FE72BCC450 | |||
0C2A8E8313C0CBD6C881236AC13E0BE893663946B5AABBDA57FFE4BA49973D547FA5DD | |||
1236DEF9FA5A9CE52F7AF1947F42A6C3502A47E8EF7E8CEFDDD44D0BFE090EA3220C2B | |||
52E11776DE36DD6C72D3B39A56F5D7295D26A69DB8CDAD1ABDBE1B21C1B754C9184E65 | |||
2CAE169E2F492FA0EE5908AC5CB3BE5F4C7F6CD9F41314D1BD9B1DE713A4E6C7DFB11D | |||
2E64000ECFBBC89326B1322A8A227ECE7B919408C9187B5C9D53FC3985833E76D72164 | |||
40B7386569E4DE270C3616ABD2A91A657AC58AA872704CB3DD4DF08C77D03D8E3CEDB5 | |||
0D83BC3837FFE45D64B457AFE9A6ABF680637C51F80CB54691233BC4DE640026ACDAAC | |||
3FC0749FA8353F6EAD5D362A63C1CF25ACA73A9CF3290B54C18DB3214AF078D918682E | |||
513C434EFA06D9045571B1734CAE42990A1BE962D6E2A45846169EAAF2CDBD520813C2 | |||
D4DE97FFFABE582A02CA893F91EAF0EBCDCEB70B35850FDDF56EEA60C845A7E5C052C4 | |||
33344776E7A4C787690CC0E13F32373EE425CA10520C251D045C0AE73EE7A0CA83C858 | |||
E2E528CDBD117BC022ED3F5DDE40CED0128B761E29B11F422C8E7C4281BF94F6F75D07 | |||
0EE58426137548ABA38019A34DF1A66F700C29EF5545AC88BED75B5036801F0D8D4DB9 | |||
C6CCEA83D9DE3D626A04A80F218EFE9C74C173412A8A86786AB4A85403E8F8292CFED5 | |||
B8BA72FB5CE1BDD094AD9D633FD482F8FDBFA540DD2224149786ADA8DB6310A7C0C6E5 | |||
9167815B2CEF34E7C458C41B5C56A79414BA57073E9B06D28CA08C56ED5E685EEA2BA5 | |||
DD112F87B253A0D02AC7CF93EDE93F48A80B2DB57B254937EA80E9AC1CEBD36FD297EB | |||
C8A3B42CBC3D2CA732891B49457F3F15AA3F9BF93553968A07CB1A834B392F27B2D152 | |||
47D93E46A6338694EA53CA0F5968109B4FAC9A#)(protected-at | |||
"20210119T215925"))) | |||
Created: 20210119T215908 |
@@ -0,0 +1,14 @@ | |||
-----BEGIN PGP PUBLIC KEY BLOCK----- | |||
mFMEWsODvRMJKyQDAwIIAQEHAgMEQLOxiiHZ/V6v3kvrhbnRtTp+oOPVpuvDKOiy | |||
gJOCZ7EWMVAwTr4syaSh8W8hdRgZ85Evv/1PYNFovYb6vzgVr7QJZWNjLWJwMjU2 | |||
iJQEExMIADwCGwMFCwkIBwIDIgIBBhUKCQgLAgQWAgMBAh4DAheAFiEEBjPF9yoZ | |||
j1HmUOSr0Mij2vngY0oFAlxVsKIACgkQ0Mij2vngY0pARAD/RozGDidH/0aFlxeU | |||
VWNJKjPiax6vdHqur5dqBS/RhhIA/1sPUnyAIvAXXID1uhK6oIBRKi7WJ5rI7vSy | |||
rBR5MlNJuFcEWsODvRIJKyQDAwIIAQEHAgMEE9Vd8dIjHJkmRs/8MLz4Krfwz5BK | |||
hunq1T0xnp65OEZJd00VxA+VUXdEUHfaDehtSv7izCpq4lbXGCkEGFN7QwMBCAeI | |||
eAQYEwgAIAIbDBYhBAYzxfcqGY9R5lDkq9DIo9r54GNKBQJcVbCpAAoJENDIo9r5 | |||
4GNK0MYA/2p5cq5smjSvKD/EGkosQwfcqkeygsQuEpDDLeEdsv8vAP9j+RHKX2tl | |||
W08zbayxGB0E+aCHuKCF8iLPeI4eroi/fw== | |||
=vsa4 | |||
-----END PGP PUBLIC KEY BLOCK----- |
@@ -0,0 +1,17 @@ | |||
-----BEGIN PGP PUBLIC KEY BLOCK----- | |||
mHMEWsOEUBMJKyQDAwIIAQELAwMEivcvlPJsPmivhJcrfHx+ORxyum57GtRhWM49 | |||
Yr8fJ48gyFqj9cLYOBdhEVvcfceyBPXmyt0TozWtjkGzgbF4LIvN1EB0DW0Rlsdn | |||
p72/hf0gnXvWZdD8euArX4RaAYuQtAllY2MtYnAzODSItAQTEwkAPAIbAwULCQgH | |||
AgMiAgEGFQoJCAsCBBYCAwECHgMCF4AWIQRbiiVMgjztmN7NEO1s8tzoVZmtogUC | |||
XFWwugAKCRBs8tzoVZmtoj1yAX9P1UV7FYpGUIP13aPP0d5Bx8HdQDAoexdXz3WW | |||
WPL/7OhSjPde23Q8TfgWyO21M2wBf1oWjOsDSjO5mDLCr7ypAFF6IJAgx76tSUe9 | |||
Qmy7sL94OWDQ4+1Dccnc9GGiHLtRI7h3BFrDhFASCSskAwMCCAEBCwMDBETUkqGr | |||
7p8kX2dm38jzzxXRh1+OL7nmY168Zeo9yfwDbnyx8BoihP9ZgPWjGXmefT78GSfw | |||
ZDaYgC2NFQOcI/b8agh3PcjrXgZaFCZbUR9v2DnLUpCF8ZbxDJwEqweNTAMBCQmI | |||
mAQYEwkAIAIbDBYhBFuKJUyCPO2Y3s0Q7Wzy3OhVma2iBQJcVbDCAAoJEGzy3OhV | |||
ma2ig1IBfifduIiwdAlD45MOolSpHMX0AT7KoJHpt9ZFvWnjQkq9ZGEA/RA9vx7Z | |||
sLb7IsG1GgF/Sn+gtf/JIteXaZMnOhEOZ2oFUufij6o8gII8/9s8mkIjkrIICy// | |||
0n3q82ndFg0c | |||
=fcpz | |||
-----END PGP PUBLIC KEY BLOCK----- |
@@ -0,0 +1,19 @@ | |||
-----BEGIN PGP PUBLIC KEY BLOCK----- | |||
mJMEWsOEhxMJKyQDAwIIAQENBAMEA28ylDnn3LR7t6EQow5DszCpmUA+yup03LsT | |||
9o0Tw4/asi8nAz+1tlRY5HD49j53PziOlsGzKYa/jxGWjhVESgqLrJp/Eo65zK9v | |||
yDhX+iCkSYQ15WGKr3QaRUmBOUbX9PqE6dY+DDGQ1kewI93QIGCB1gn+OSmyKPm3 | |||
YaVIbo60CWVjYy1icDUxMojUBBMTCgA8AhsDBQsJCAcCAyICAQYVCgkICwIEFgID | |||
AQIeAwIXgBYhBExZq5JyqmofYLhb0KpcWNFPe49IBQJcVbDYAAoJEKpcWNFPe49I | |||
F8UB/i8DwypbNusdbAqTbu1Twpn/QFMaVKHn8Ysgzqpimv+6hRq7uzekCvEOPOjl | |||
Oke5yLp8bpTTMPRKyfjNatQhdn8B/2+54qtXJuQd9oTSz7f2eFYvA8ZsMQgApYNl | |||
ksvKSw6dhSNX/DXK7JYIlgZXx7UGTkP4h3FQSiyCUJhVpClVVGa4lwRaw4SHEgkr | |||
JAMDAggBAQ0EAwRCtEqQkEgzQDxNGCj0duj0aGvnH+DHKlP4V6p9LJVIL0TuF1uZ | |||
BzP04efRvZT2vzCTcvvdE/G6G/txEZsR/989OchbkVWOPM/oCVktkaA02rBMcefh | |||
k9wKD+O9E3oHEN+tBt3yhmsv0MIR9IfPwi1GCsu55p4WUI//+ysB2T0YaQMBCgmI | |||
uAQYEwoAIAIbDBYhBExZq5JyqmofYLhb0KpcWNFPe49IBQJcVbDgAAoJEKpcWNFP | |||
e49IZQUB/R4U4aWBMimZSL8kyaK+/Y8NcInIGqRzrm5zPnTSHrgQeH55SVKahzsq | |||
j57D1Ec1HnUd4ctISVocOxpUfnJq5NAB/1fzbh+1RN2ZyNW6tAJlA/Irkwzzbil9 | |||
6fAIvRolwwaGsUZNMEiCF3rTcFaenJg9UhQvX6BoqXCdwawqTZCRN6E= | |||
=h+On | |||
-----END PGP PUBLIC KEY BLOCK----- |
@@ -0,0 +1,44 @@ | |||
-----BEGIN PGP PUBLIC KEY BLOCK----- | |||
mQMuBFrDf8YRCACHbPXT8jG3RNWdNms9xvdaiLrY+Iui1Gq2WGLSajPEZVASEWN+ | |||
JuuX8k9d05rb+F2VAqLnW3CQreDW6unVNeRf52tdM8J4eXmeu/Bkk8y1Qx/HbGca | |||
sAGSIEKg34TuV5Ly5m4Z07bs3HPYUUQbmu0uclGfnX/ArZ+4Jp+uypC9bErdiXM0 | |||
cM7d52tb9IvOlXNu23rzHDbgVP6qF/AxLSRD8SQPvshu3/5b0bvdBkHVk+dHoLO0 | |||
fC5j476ibHGGZcnPMrTwqEIAxUCy5wQ3Lb/2/G31kuV55bAZ41tUNEvfzbiRN1L5 | |||
1uiO+XX96bRqLN13t0Coaba9fq1aN5Zr6piXAQCuNzvj8aaLXAOEXVRej6a2k+/C | |||
Jny91MgjSM701twUDQf/RMWHwQuFPe6zSDQs4pWlxkHwXJw3AVidkoWg/DCwv2pJ | |||
5VYQxBXRwND2OhcZvmeDT94UzPws0dFbprWyymtA49ZXitPGzFARAFWHWxk/IsOf | |||
Idc6w5eHXDMHxLhiPFqfjKeNpibzO2P7LXP2bUKzwybkKZarz1N6pfanDXAtC4DU | |||
SC3qWNqywYlfINAGCdwsPu5qFUNSnkjTYxe/MiHb4kL1p/z8qFNWrvg6GryXygp5 | |||
cLdqckjPaUHlR+B9wQZIVRzVdlFAbMDJ0EERLFG7FbIuY8dzy5x7n+oBOgRxee2I | |||
ytUpGVMLIJuecARLXNKsMXviCMYVE7Tj5hiSoM0TIgf8CwLLFsSa0EDm/wlXYZMj | |||
2gg3Z8iCz6ycxvFD9PXNt+8jyELO8CwS2pWu7ptBgaugkinqd40EQslQoP76CcHq | |||
bGQEohm4SnmfGsV8dicuziMVVKkVrYgbGvZ5cQ/ONGTTnSJuiTPN19oztwh8JOEc | |||
Jd4l+wFuVSm8OS1mj5eexeX1Tz3NfWQMT4deKh+jiTLe9Sw/57sSjxiw/8IczqhN | |||
Fu3YIy40d3Bv+OF6i8I+94WLbJPiX1ban1wqcA0bMaps1aYVtTRZ+mP0b3M9W7qa | |||
383/SLCBjUzQ7zm6PX/7uAXFyZOQfcyLaJ8Hc34yOE0git1DWmRS7U16Zv54v1Uh | |||
HbQGZHNhLWVniJQEExEIADwCGwMFCwkIBwIDIgIBBhUKCQgLAgQWAgMBAh4DAheA | |||
FiEECRxEzpz7w/9+x6ZNyKEKfXgnPhAFAlxVr2sACgkQyKEKfXgnPhABkgD7BfEd | |||
jhB+ApC9icNLs893i2jiHbAxZGSOQMRhCaJ7AzoBAIipcSIsBa3LJ4eTec1esiCY | |||
a8xzxquCTA+oANNoX7p6uQMNBFrDf8YQDADMPZ6+/YAIjXMLfQKX80jz6FZ4Kdfx | |||
Dc60m4O+ZElMv7eXtQJC2L/xOh4Th1fZUQIhSdtFiwaCSkCD8occfJwyt+lH3Dj0 | |||
Qrh3mIycAfPrjj0Rgxz8nRQbBLDbLF1QGPimt0zP69ByJ3opLujVVi5ixwgwza9S | |||
eGffKwGdyb9uFcB9MVnC997zfLvx/uNV44BwLnCH6Tp68Lynf+FpuvSX+Rsj4li3 | |||
UiLoVxEIbBZ/5Bn3ygc7aW4fM4bR4hKjWwJR0Hh1y/kt6A7dEAypVKBfSqAtAu2A | |||
zYAq3USsbtq1X1FaGEsmvcJY8IGa+aLTArq7dkhXzcv7K3EVdqOawS1zS/ARuG+B | |||
k6kct3zzyj1EitiTvdMAkGOoyk16qKVzUcbFRVC1HsZtxYj6OxU4Eazvh0LjvZ0A | |||
+eft/XO/ZmN6vyRaF1/10z+uHPfj1FLMpS8Zn4SN6x7Qtsx3iLL1n9cKBDFRXCqD | |||
HDaxLVC3N4zAI2hMMmZid+fbTuhsqYbSX2cAAwUL/j/H4/9Ml7PUUCXoozX2V4K+ | |||
gi6WEYmY+pXN/we9NuFulW4aURo7jK4wRYBu0BS3K9e8f8WUMAV5V6ShPWHXcobt | |||
iuSjLYJwdBJkgHbnKFWPZUozJ3Ftyp0Lh1M5bN7/ECofAxLHbRpCVrcOP2LC7vAU | |||
AeMgdiFDqEiLCnr/aGvqUOxbGO6Isi4jvaM/ZUfGjGe/Z6yVoqm6wEsNM7+9cFGQ | |||
QR1lRPeCPKcLeasCdbM5EIt1aLFNijZigWuDRLIgG5PuzA8Kpdk/u/UuCUeUFwJN | |||
ym8MEv2JJDiWHmb8IcgFMp40VenUs0fte0LWwrMjWVPpLsHKmkraRjQ1UtarRhT0 | |||
ANYilGjZWCnCb11xGKhlM7r5IkLGY/L/Eh4vjLgg9T5rGwOF8p1jSgx9mA8SpHV0 | |||
O0BoKNX1ApWEHayTLcyayCnTYbY/e4axnSKodixAI/NghOnJHqGr4LeZeKk/Q0mm | |||
GlljzFv3EAdoru4DVowWGFBmrwBy7o+GLgHs6K/+yIh4BBgRCAAgAhsMFiEECRxE | |||
zpz7w/9+x6ZNyKEKfXgnPhAFAlxVr64ACgkQyKEKfXgnPhCETQEApruWUqCwfibQ | |||
vyI/OZohPzljlvIoioj3rFjYNpufQD8A/RTaYtnPiEvsPynEZCj9zTV/SuHiKbHS | |||
v5BhpoOOm+jM | |||
=PnGk | |||
-----END PGP PUBLIC KEY BLOCK----- |
@@ -0,0 +1,9 @@ | |||
-----BEGIN PGP PUBLIC KEY BLOCK----- | |||
mDMEWsN6MBYJKwYBBAHaRw8BAQdAAS+nkv9BdVi0JX7g6d+O201bdKhdowbielOo | |||
ugCpCfi0CWVjYy0yNTUxOYiUBBMWCAA8AhsDBQsJCAcCAyICAQYVCgkICwIEFgID | |||
AQIeAwIXgBYhBCH8aCdKrjtd45pCd8x4YniYGwcoBQJcVa/NAAoJEMx4YniYGwco | |||
lFAA/jMt3RUUb5xt63JW6HFcrYq0RrDAcYMsXAY73iZpPsEcAQDmKbH21LkwoClU | |||
9RrUJSYZnMla/pQdgOxd7/PjRCpbCg== | |||
=miZp | |||
-----END PGP PUBLIC KEY BLOCK----- |
@@ -0,0 +1,23 @@ | |||
Meta-Description: Example from GPG file 'keyformat.txt'. | |||
Description: Key to sign all GnuPG released tarballs. | |||
The key is actually stored on a smart card. | |||
Use-for-ssh: yes | |||
OpenSSH-cert: long base64 encoded string wrapped so that this | |||
key file can be easily edited with a standard editor. | |||
Token: D2760001240102000005000011730000 OPENPGP.1 | |||
Token: FF020001008A77C1 PIV.9C | |||
Key: (shadowed-private-key | |||
(rsa | |||
(n #00AA1AD2A55FD8C8FDE9E1941772D9CC903FA43B268CB1B5A1BAFDC900 | |||
2961D8AEA153424DC851EF13B83AC64FBE365C59DC1BD3E83017C90D4365B4 | |||
83E02859FC13DB5842A00E969480DB96CE6F7D1C03600392B8E08EF0C01FC7 | |||
19F9F9086B25AD39B4F1C2A2DF3E2BE317110CFFF21D4A11455508FE407997 | |||
601260816C8422297C0637BB291C3A079B9CB38A92CE9E551F80AA0EBF4F0E | |||
72C3F250461E4D31F23A7087857FC8438324A013634563D34EFDDCBF2EA80D | |||
F9662C9CCD4BEF2522D8BDFED24CEF78DC6B309317407EAC576D889F88ADA0 | |||
8C4FFB480981FB68C5C6CA27503381D41018E6CDC52AAAE46B166BDC10637A | |||
E186A02BA2497FDC5D1221#) | |||
(e #00010001#) | |||
(shadowed t1-v1 | |||
(#D2760001240102000005000011730000# OPENPGP.1) | |||
))) |
@@ -0,0 +1,14 @@ | |||
-----BEGIN PGP PUBLIC KEY BLOCK----- | |||
mFIEWsOCNRMIKoZIzj0DAQcCAwQS5G6mn5dhamZ6678SXE1azavqf8BItWO9Qv8V | |||
dS1vEEoD14urr5OQKTLuHhDRjvSQdaxRtkf0sI51T7230sT3tAhlY2MtcDI1NoiU | |||
BBMTCAA8AhsDBQsJCAcCAyICAQYVCgkICwIEFgIDAQIeAwIXgBYhBLVP3ru2c0I6 | |||
XQqlRCNnTyGyRBUnBQJcVa/nAAoJECNnTyGyRBUn1ycA+wVg9sEfHDBaGtLqlUSB | |||
WdGKURrHN7CJe2UTz1/7oQCBAQDDi4RQyLHs+TfOrBNSbLEswCu1oEh8VmHt/SN7 | |||
+mqNLbhWBFrDgjUSCCqGSM49AwEHAgMELDOArLIG85ABQu1IwgQMpiIuUwj+N7ib | |||
gGenTRck5dkBpX48eK3lbjovXn4YkBneA7z14iez3+Sdg6UFAMFV2QMBCAeIeAQY | |||
EwgAIAIbDBYhBLVP3ru2c0I6XQqlRCNnTyGyRBUnBQJcVa/vAAoJECNnTyGyRBUn | |||
ZKoBAJ64gv3w27nFBERvIsRqufvR6xcimqS7Gif+WehBU+P5AQC5bqoISh0oSQid | |||
adI84f60RuOaozpjvR3B1bPZiR6u7w== | |||
=H2xn | |||
-----END PGP PUBLIC KEY BLOCK----- |
@@ -0,0 +1,16 @@ | |||
-----BEGIN PGP PUBLIC KEY BLOCK----- | |||
mG8EWsOCnBMFK4EEACIDAwTRTCLBHlq6SUTiXZfWR0vbUh/0VAlrePaqHVIE4LEK | |||
0UBhPCIQOGuGL4JIufc8UzBWhiMLJ0z7KRjBWufsMZR2+rqj+unOK2lLu7sc9or8 | |||
X6B74hhP3Ili24PgGFAeAG+0CGVjYy1wMzg0iLQEExMJADwCGwMFCwkIBwIDIgIB | |||
BhUKCQgLAgQWAgMBAh4DAheAFiEEqyXLoELdkkw6zD7TJCo6peqF9EoFAlxVsBEA | |||
CgkQJCo6peqF9EooJQF7BPZelriXwZ/kJzaamImHBddkLFc7d2WbuSfDxEZQ+Mfw | |||
BAP3+QYUaFtfeqApjY69AX4w6LhTUgI2kl4O0Vc7ZOlqZBlwAc8CMV08TTfOEio2 | |||
b51SItvhLdDrFRJ2K4jiO+a4cwRaw4KcEgUrgQQAIgMDBORWqhYflSrYzF04SK8q | |||
8Om+DYTvwRtUlr3Aoq44+gm5yBcmJmgT3TKrp/bx5Jg/zwzIASFn0agbxkqKpQqH | |||
sHeelWsSBROQzy98HXdCp3nVmghI2aDk8zdD6AV4m7c2ewMBCQmImAQYEwkAIAIb | |||
DBYhBKsly6BC3ZJMOsw+0yQqOqXqhfRKBQJcVbAZAAoJECQqOqXqhfRKgAIBf3Wk | |||
TsqUA1JXkPGetA9sjHglIICN+DZY5k+PwTJUxaW2zrkiPJ3BYEnKbmmBLzA7BgGA | |||
4RYatyl2WOUYh/poRLgu7JpE4oRqdmNA+QOpCILMId1AeXfj4W01RKFWaKeH+3Yy | |||
=2H/0 | |||
-----END PGP PUBLIC KEY BLOCK----- |
@@ -0,0 +1,19 @@ | |||
-----BEGIN PGP PUBLIC KEY BLOCK----- | |||
mJMEWsODGxMFK4EEACMEIwQA8OZCJ8Iv4Qr2oRr0kqez0nPSW/vNxYBZRpCJ9ab8 | |||
kVaRhW7+5vEsecm5XugZSePbAmERmk3oEiSgu35r6aovowcAGrOBfBm9fyIVqaoX | |||
veTS3yRHH6NEf044+pC+uBaaFukkrDmVTecdRvYr3Yrdc5ifyOve053znlpQ6a4n | |||
9bh4GGy0CGVjYy1wNTIxiNYEExMKADwCGwMFCwkIBwIDIgIBBhUKCQgLAgQWAgMB | |||
Ah4DAheAFiEET7Of9vpIV6S9fvW0IJLKgyQmO2oFAlxVsCkACgkQIJLKgyQmO2oK | |||
DwIGO72zo6otVkbHfeI9hWx/8FAOXh4MT4YtDicF/sj8QbHzdbEBHcLCByLYAnph | |||
8VVoCxpPcBLmNSHbNyreoksjEE0CB10P5kPrd/bYkdNx/HTu+1i8f7a448+Nr2rF | |||
PwdI9tOsghkT41qobZavjjnBlT/xv5DqXldJkEfaeiJxPHOKvMhWuJcEWsODGxIF | |||
K4EEACMEIwQBAY7ZCAjks1MWWxibg/EVaz5t6iEKJTwu8mGGKWdPZAQRKKNtNpf0 | |||
pZAMV3I8ue/WQMsYKRYv5AGq1PnjV19DmLsA0aGw4MDM260coctkcn/2MAJQMC9+ | |||
3Z+BJS3hqzwDuZ+LS13r0RLpgnt3ks+3ucG4II38ZZ1lTwKoIc+w/OuhsOIDAQoJ | |||
iLsEGBMKACACGwwWIQRPs5/2+khXpL1+9bQgksqDJCY7agUCXFWwLgAKCRAgksqD | |||
JCY7ahqbAgkBiXYtiBlp5dmSYnbc4JoIYWcxTBQ+/dGHyU6ZEfC5VQz2mrdJetK1 | |||
bIID0rFSsd24/8IzAqM3L+nY9h9bULWroroCBjTohh0j2EbW+hFOrRqL01osnlY+ | |||
1/G8e44blB5JqsPI9FqOZOUj6IzsUuV1N9gJbm1RHu/hSpm52d6rX4nOTbqt | |||
=3jnl | |||
-----END PGP PUBLIC KEY BLOCK----- |
@@ -0,0 +1,40 @@ | |||
-----BEGIN PGP PUBLIC KEY BLOCK----- | |||
mQGNBFrDgZ4BDACxg83nNkYvIAz6Nk4Np4BscWDrrL3tyiiQSz1yfCotxO8zUtVl | |||
JorSlqRurNdAYU2XJLakMqpQHE7VVIMI/WdXCC8CrQbULOBxIf/LGiQf0VDo8ukg | |||
iFFd5vUeMRRILWKnMc/GCmFkFOUHd1Y60h96oe5f/d286fRZQnCO8PGS8CgB1mDJ | |||
GBY8U1DCCkt6g9O6bfFkfcwetr1+kB2cBIY7uDzN1Sm8dz2VrkqPqtqt/F02giRN | |||
hD2GxX9HVYCkCKEE9DFsB+MDKR9z5lXrI3SAsL/3htXK/ukWgBe4DIFjTXbrzgP+ | |||
nuWb4s1NxwSD7yjlnqVD7mZTGAwMsQbCjhwIeY4t5onsCJ4/40zm1jIZ5TJsfQ/b | |||
5gA7iu9kMKzxO0bPI2loJEB/K9aH7qsyQHgtt7G3+G8JrixLBUobtreY9V2QvhtC | |||
Q4OAHRO5nIKYIazDuKv9bxautZ0WuLOk/qmWMMoqFAn5bzQeVTTBD6kvr6l8/+MZ | |||
zaij9YPeUbeKJuMAEQEAAbQHcnNhLXJzYYkB0gQTAQIAPAIbAwULCQgHAgMiAgEG | |||
FQoJCAsCBBYCAwECHgMCF4AWIQRrwEpaPds1dmuaQNgvuReRGImOiwUCXFWwhwAK | |||
CRAvuReRGImOi1NiC/0RPjbQTWMw4/GxIMkRb9kmmsSUe7kCgqBCqZTxcI7rxZdn | |||
JFDbP5c6DAS/11bRQJ9OsoUjbDx0d81UuBlXB/mNsb9nCcXOrqAUHRqgWoNSDk4g | |||
9Oa1Kx77OM9BvRJbJGch2YW5Wcch5vcqQNu+6x3VGt7ipljYEJSQ6Dre+dgxYjXK | |||
60x63/ulFk2XImPQYjQ8VHbW/HDg/+DLf++phjVy9l58U1sUKSSdO8uuYoW6dBv2 | |||
xRg18Sn3DWOU+mrkV8Ld95+NRRE1cSHTQv5hu4ELqrV+YdGNmv9DgQAMJOl3xy8i | |||
vOz4cpKaOBasm423wr5Y56nOTzLFN+dxnYR8tbqswLkCldRY6fvL1NsS77rj0yZp | |||
pecCyi0E6RAcmSiZJqpnOpcuI76AkZuWSDEP3Y3x5QBf5fu2uiQQsPXYN8ie6xcC | |||
zeYtXsHyNxF0oBh2c26po8fo4E4T70RSO8Oqs1XzXnjIIle8pKU9M5U5ISbWS3Hj | |||
vtOn5ZrLC5KYnVRna3G5AY0EWsOBngEMALmXpJoPC1m4THYrfHibtt2/OwAlDm20 | |||
3xn+Klw69bkeXdc71wsLOAHVL3+7gXpip+IYmN7CBIyqlOCtsluu+gwP3MczPJZX | |||
vk1uXMMfLKiXl9Kodx/Fqq0Y26Tqse5PPMlagPStIvKyT0WTa3RCD28uVklapLuy | |||
1w1k4G5hIDPt6uKyxXq/HzneRSGWafmqoCWOmXQZzfOMG249bMXNOcPMJhOejPRS | |||
jREnnntbpvZ8DU/38+JFtqCUkPwuqYQkvGCKReSBifMiG3XAhHWOGzXPzdW1XdAi | |||
aA+NQP/kMUs45jS3hDdp4EObllYRBsQwtFpKPMNmwaVuOmVlbrXTP0YsDYGndkE6 | |||
5nJ46/2xPhl8+nIgDLg3SBBzQdOiPOGtHYjs0bRKdwXTeAq4fDq0vCQXMJF2fwAQ | |||
LEYWs7kabKhcPpWzTtoCG78WzR3TgldEPhPjE0offvVQO56x1XDqMBctoiDWkWkS | |||
bdi03GhbFdK5A7uYBTJYEoo61Yp/2/MjyQARAQABiQG2BBgBAgAgAhsMFiEEa8BK | |||
Wj3bNXZrmkDYL7kXkRiJjosFAlxVsI8ACgkQL7kXkRiJjoumjwv/c6a9G1bi3sh7 | |||
wRFhpsrFUoFfEBDI4eyI/haWhCIfI8n7p3lCSIy8lmf9yvUs75d5M3EQW08NQjIs | |||
/o8FcFoUBnKQv2whWSHTpx/BkuhcNVY+NIwyBKomU0WkFSm3+80ix0uh97KSlRlW | |||
Q5nMVExNxZ4mRFAhDQrJ2/pZ2DaddeO+4uZ7Twquaix+PMxpNKvkj2+757L23YjF | |||
QmHdk6E8burofpSCfBTB84eUSDvzs6Eb/34/KlbBZhKMYdffMDCSAZIMfIav6YVJ | |||
UDzT40kmS0vRW6bDIetSbpBM+GD1cSq0wKdlt+Giur9ZiaiyHIEqbPgr6WgdND25 | |||
Vx/i23Ik2o8wMb0Ub8cKD9wjdGAk+Rt2r7d2RzyO/R3ThKbUOGkQX6acAAZAjhPs | |||
UGxt1dDojmQ3nF4l2hZ9PcsyD3pz226wUUPT4JA1eE6tdoVjzY2J7EhfNaVcQQlb | |||
bQJQ+BQcO4oP1mPRCx1GiSmB+jRNQ4npxVJxLO/j7T27CrSZbhT7 | |||
=aibx | |||
-----END PGP PUBLIC KEY BLOCK----- |
@@ -0,0 +1,14 @@ | |||
-----BEGIN PGP PUBLIC KEY BLOCK----- | |||
mE8EWsOE8xMFK4EEAAoCAwQVHqFZWqedXUIkNFs82PsQB3bsCDhrL/73xZca3+vo | |||
kB4T7jHcACThuMZYuUqUo9NzNTJioluOvZG+UdYXPdfdtAplY2MtcDI1NmsxiJQE | |||
ExMIADwCGwMFCwkIBwIDIgIBBhUKCQgLAgQWAgMBAh4DAheAFiEEgfdytX1Ov+cA | |||
CmYjPqW7b5aSwaAFAlxVsQ0ACgkQPqW7b5aSwaD2tQD/R4d15NBuSJ6IB1brH0E9 | |||
nEWkqo892PaAY5akdCO/i9EBAMsjE5NPxBnCs03c+VHFU200k27ixdrWpUa+HZEI | |||
A5wSuFMEWsOE8xIFK4EEAAoCAwSUWwe7CaaOYRANiKet2evLiOumefIHuvRpyOSK | |||
hyRdclIWpBUCAWEnmalkEL/8cEM5fjtILtCOKXqCOBsPv45HAwEIB4h4BBgTCAAg | |||
AhsMFiEEgfdytX1Ov+cACmYjPqW7b5aSwaAFAlxVsRUACgkQPqW7b5aSwaCETgD/ | |||
YXzCMYMbPGAU2oTitjAno8hDWmgTeaFWeCmqf6l9mP8BAKvpewWeFGZfWGAQcWPi | |||
E+jv7vadvEt1yMA8rmT041F5 | |||
=mDCI | |||
-----END PGP PUBLIC KEY BLOCK----- |
@@ -0,0 +1,13 @@ | |||
-----BEGIN PGP PUBLIC KEY BLOCK----- | |||
mDMEW8SVGhYJKwYBBAHaRw8BAQdA9SMZ2uw0YugMFcl5TpEZeBRAGniEk9a42XNs | |||
7QA4Tky0DGVkZHNhLXgyNTUxOYiQBBMWCAA4FiEETJc4pvK+Thp5bJt7lBgioPwb | |||
MKUFAlvElRoCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQlBgioPwbMKUi | |||
1wEAgMq3X7o17OJBPfY3He/exDR6LhWwAAXrVQR/WdRiHkEBALd1Mj0BlZZLoKTr | |||
uJ4MD5CYZLicXTRwOv6e52F/DHwJuDgEW8SVGhIKKwYBBAGXVQEFAQEHQA0Lh2mG | |||
lB1O4xDYgztm/aX7+8AdHEGaMsCF1RQ6wVUeAwEIB4h4BBgWCAAgFiEETJc4pvK+ | |||
Thp5bJt7lBgioPwbMKUFAlvElRoCGwwACgkQlBgioPwbMKXmlQD+KxVg2dGL8lRW | |||
rQajwzmuwMrJX1lvJylg5Ozk6SGrBeABANZrdt8bmArEqeRVxFO2F4P7btyIpf1w | |||
5aNpqqtvkRcB | |||
=EYfV | |||
-----END PGP PUBLIC KEY BLOCK----- |
@@ -1,5 +1,5 @@ | |||
/* | |||
* Copyright (C) 2019, Thomas Wolf <thomas.wolf@paranor.ch> and others | |||
* Copyright (C) 2019, 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others | |||
* | |||
* This program and the accompanying materials are made available under the | |||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||
@@ -53,7 +53,7 @@ public class BouncyCastleGpgKeyLocatorTest { | |||
assertFalse(match(USER_ID, "<heinrichh>")); | |||
assertFalse(match(USER_ID, "<uni-duesseldorf>")); | |||
assertFalse(match(USER_ID, "<h@u>")); | |||
assertFalse(match(USER_ID, "<HeinrichH@uni-duesseldorf.de>")); | |||
assertTrue(match(USER_ID, "<HeinrichH@uni-duesseldorf.de>")); | |||
assertFalse(match(USER_ID.substring(0, USER_ID.length() - 1), | |||
"<heinrichh@uni-duesseldorf.de>")); | |||
assertFalse(match("", "<>")); | |||
@@ -72,8 +72,8 @@ public class BouncyCastleGpgKeyLocatorTest { | |||
assertFalse(match(USER_ID, "@ ")); | |||
assertFalse(match(USER_ID, "@")); | |||
assertFalse(match(USER_ID, "@Heine")); | |||
assertFalse(match(USER_ID, "@HeinrichH")); | |||
assertFalse(match(USER_ID, "@Heinrich")); | |||
assertTrue(match(USER_ID, "@HeinrichH")); | |||
assertTrue(match(USER_ID, "@Heinrich")); | |||
assertFalse(match("", "@")); | |||
assertFalse(match("", "@h")); | |||
} | |||
@@ -110,6 +110,7 @@ public class BouncyCastleGpgKeyLocatorTest { | |||
public void testExplicitFingerprint() throws Exception { | |||
assertFalse(match("John Fade <j.fade@example.com>", "0xfade")); | |||
assertFalse(match("John Fade <0xfade@example.com>", "0xfade")); | |||
assertFalse(match("John Fade <0xfade@example.com>", "0xFADE")); | |||
assertFalse(match("", "0xfade")); | |||
} | |||
@@ -128,7 +129,7 @@ public class BouncyCastleGpgKeyLocatorTest { | |||
assertTrue(match("John Fade <0xfade@example.com>", "*0xfade")); | |||
assertTrue(match("John Fade <0xfade@example.com>", "*0xFADE")); | |||
assertTrue(match("John Fade <0xfade@example.com>", "@0xfade")); | |||
assertFalse(match("John Fade <0xfade@example.com>", "@0xFADE")); | |||
assertTrue(match("John Fade <0xfade@example.com>", "@0xFADE")); | |||
assertFalse(match("", "0x")); | |||
} | |||
} |
@@ -0,0 +1,61 @@ | |||
/* | |||
* Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others | |||
* | |||
* This program and the accompanying materials are made available under the | |||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||
* https://www.eclipse.org/org/documents/edl-v10.php. | |||
* | |||
* SPDX-License-Identifier: BSD-3-Clause | |||
*/ | |||
package org.eclipse.jgit.gpg.bc.internal.keys; | |||
import static org.junit.Assert.assertEquals; | |||
import java.math.BigInteger; | |||
import java.util.Locale; | |||
import org.bouncycastle.openpgp.PGPException; | |||
import org.bouncycastle.util.encoders.Hex; | |||
import org.eclipse.jgit.util.sha1.SHA1; | |||
import org.junit.Test; | |||
public class KeyGrip25519Test { | |||
interface Hash { | |||
byte[] hash(SHA1 sha, BigInteger q) throws PGPException; | |||
} | |||
private void assertKeyGrip(String key, String expectedKeyGrip, Hash hash) | |||
throws Exception { | |||
SHA1 grip = SHA1.newInstance(); | |||
grip.setDetectCollision(false); | |||
BigInteger pk = new BigInteger(key, 16); | |||
byte[] keyGrip = hash.hash(grip, pk); | |||
assertEquals("Keygrip should match", expectedKeyGrip, | |||
Hex.toHexString(keyGrip).toUpperCase(Locale.ROOT)); | |||
} | |||
@Test | |||
public void testCompressed() throws Exception { | |||
assertKeyGrip("40" | |||
+ "773E72848C1FD5F9652B29E2E7AF79571A04990E96F2016BF4E0EC1890C2B7DB", | |||
"9DB6C64A38830F4960701789475520BE8C821F47", | |||
KeyGrip::hashEd25519); | |||
} | |||
@Test | |||
public void testCompressedNoPrefix() throws Exception { | |||
assertKeyGrip( | |||
"773E72848C1FD5F9652B29E2E7AF79571A04990E96F2016BF4E0EC1890C2B7DB", | |||
"9DB6C64A38830F4960701789475520BE8C821F47", | |||
KeyGrip::hashEd25519); | |||
} | |||
@Test | |||
public void testCurve25519() throws Exception { | |||
assertKeyGrip("40" | |||
+ "918C1733127F6BF2646FAE3D081A18AE77111C903B906310B077505EFFF12740", | |||
"0F89A565D3EA187CE839332398F5D480677DF49C", | |||
KeyGrip::hashCurve25519); | |||
} | |||
} |
@@ -0,0 +1,143 @@ | |||
/* | |||
* Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others | |||
* | |||
* This program and the accompanying materials are made available under the | |||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||
* https://www.eclipse.org/org/documents/edl-v10.php. | |||
* | |||
* SPDX-License-Identifier: BSD-3-Clause | |||
*/ | |||
package org.eclipse.jgit.gpg.bc.internal.keys; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertTrue; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.security.Security; | |||
import java.util.Iterator; | |||
import java.util.Locale; | |||
import java.util.function.Consumer; | |||
import org.bouncycastle.jce.provider.BouncyCastleProvider; | |||
import org.bouncycastle.openpgp.PGPException; | |||
import org.bouncycastle.openpgp.PGPPublicKey; | |||
import org.bouncycastle.openpgp.PGPPublicKeyRing; | |||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; | |||
import org.bouncycastle.openpgp.PGPUtil; | |||
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; | |||
import org.bouncycastle.util.encoders.Hex; | |||
import org.junit.BeforeClass; | |||
import org.junit.Test; | |||
import org.junit.runner.RunWith; | |||
import org.junit.runners.Parameterized; | |||
import org.junit.runners.Parameterized.Parameter; | |||
import org.junit.runners.Parameterized.Parameters; | |||
@RunWith(Parameterized.class) | |||
public class KeyGripTest { | |||
@BeforeClass | |||
public static void ensureBC() { | |||
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { | |||
Security.addProvider(new BouncyCastleProvider()); | |||
} | |||
} | |||
protected static class TestData { | |||
String filename; | |||
String[] expectedKeyGrips; | |||
TestData(String filename, String... keyGrips) { | |||
this.filename = filename; | |||
this.expectedKeyGrips = keyGrips; | |||
} | |||
@Override | |||
public String toString() { | |||
return filename; | |||
} | |||
} | |||
@Parameters(name = "{0}") | |||
public static TestData[] initTestData() { | |||
return new TestData[] { | |||
new TestData("rsa.asc", | |||
"D148210FAF36468055B83D0F5A6DEB83FBC8E864", | |||
"A5E4CD2CBBE44A16E4D6EC05C2E3C3A599DC763C"), | |||
new TestData("dsa-elgamal.asc", | |||
"552286BEB2999F0A9E26A50385B90D9724001187", | |||
"CED7034A8EB5F4CE90DF99147EC33D86FCD3296C"), | |||
new TestData("brainpool256.asc", | |||
"A01BAA22A72F09A0FF0A1D4CBCE70844DD52DDD7", | |||
"C1678B7DE5F144C93B89468D5F9764ACE182ED36"), | |||
new TestData("brainpool384.asc", | |||
"2F25DB025DEBF3EA2715350209B985829B04F50A", | |||
"B6BD8B81F75AF914163D97DF8DE8F6FC64C283F8"), | |||
new TestData("brainpool512.asc", | |||
"5A484F56AB4B8B6583B6365034999F6543FAE1AE", | |||
"9133E4A7E8FC8515518DF444C3F2F247EEBBADEC"), | |||
new TestData("nistp256.asc", | |||
"FC81AECE90BCE6E54D0D637D266109783AC8DAC0", | |||
"A56DC8DB8355747A809037459B4258B8A743EAB5"), | |||
new TestData("nistp384.asc", | |||
"A1338230AED1C9C125663518470B49056C9D1733", | |||
"797A83FE041FFE06A7F4B1D32C6F4AE0F6D87ADF"), | |||
new TestData("nistp521.asc", | |||
"D91B789603EC9138AA20342A2B6DC86C81B70F5D", | |||
"FD048B2CA1919CB241DC8A2C7FA3E742EF343DCA"), | |||
new TestData("secp256k1.asc", | |||
"498B89C485489BA16B40755C0EBA580166393074", | |||
"48FFED40D018747363BDEFFDD404D1F4870F8064"), | |||
new TestData("ed25519.asc", | |||
"940D97D75C306D737A59A98EAFF1272832CEDC0B"), | |||
new TestData("x25519.asc", | |||
"A77DC8173DA6BEE126F5BD6F5A14E01200B52FCE", | |||
"636C983EDB558527BA82780B52CB5DAE011BE46B") | |||
}; | |||
} | |||
// Injected by JUnit | |||
@Parameter | |||
public TestData data; | |||
private void readAsc(InputStream in, Consumer<PGPPublicKey> process) | |||
throws IOException, PGPException { | |||
PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection( | |||
PGPUtil.getDecoderStream(in), new JcaKeyFingerprintCalculator()); | |||
Iterator<PGPPublicKeyRing> keyRings = pgpPub.getKeyRings(); | |||
while (keyRings.hasNext()) { | |||
PGPPublicKeyRing keyRing = keyRings.next(); | |||
Iterator<PGPPublicKey> keys = keyRing.getPublicKeys(); | |||
while (keys.hasNext()) { | |||
process.accept(keys.next()); | |||
} | |||
} | |||
} | |||
@Test | |||
public void testGrip() throws Exception { | |||
try (InputStream in = this.getClass() | |||
.getResourceAsStream(data.filename)) { | |||
int index[] = { 0 }; | |||
readAsc(in, key -> { | |||
byte[] keyGrip = null; | |||
try { | |||
keyGrip = KeyGrip.getKeyGrip(key); | |||
} catch (PGPException e) { | |||
throw new RuntimeException(e); | |||
} | |||
assertTrue("More keys than expected", | |||
index[0] < data.expectedKeyGrips.length); | |||
assertEquals("Wrong keygrip", data.expectedKeyGrips[index[0]++], | |||
Hex.toHexString(keyGrip).toUpperCase(Locale.ROOT)); | |||
}); | |||
assertEquals("Missing keys", data.expectedKeyGrips.length, | |||
index[0]); | |||
} | |||
} | |||
} |
@@ -0,0 +1,163 @@ | |||
/* | |||
* Copyright (C) 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others | |||
* | |||
* This program and the accompanying materials are made available under the | |||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||
* https://www.eclipse.org/org/documents/edl-v10.php. | |||
* | |||
* SPDX-License-Identifier: BSD-3-Clause | |||
*/ | |||
package org.eclipse.jgit.gpg.bc.internal.keys; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertFalse; | |||
import static org.junit.Assert.assertNotNull; | |||
import static org.junit.Assert.assertTrue; | |||
import java.io.BufferedInputStream; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.security.Security; | |||
import java.util.Iterator; | |||
import javax.crypto.Cipher; | |||
import org.bouncycastle.jce.provider.BouncyCastleProvider; | |||
import org.bouncycastle.openpgp.PGPException; | |||
import org.bouncycastle.openpgp.PGPPublicKey; | |||
import org.bouncycastle.openpgp.PGPPublicKeyRing; | |||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; | |||
import org.bouncycastle.openpgp.PGPSecretKey; | |||
import org.bouncycastle.openpgp.PGPUtil; | |||
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; | |||
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; | |||
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; | |||
import org.junit.BeforeClass; | |||
import org.junit.Test; | |||
import org.junit.runner.RunWith; | |||
import org.junit.runners.Parameterized; | |||
import org.junit.runners.Parameterized.Parameter; | |||
import org.junit.runners.Parameterized.Parameters; | |||
@RunWith(Parameterized.class) | |||
public class SecretKeysTest { | |||
@BeforeClass | |||
public static void ensureBC() { | |||
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { | |||
Security.addProvider(new BouncyCastleProvider()); | |||
} | |||
} | |||
private static volatile Boolean haveOCB; | |||
private static boolean ocbAvailable() { | |||
Boolean haveIt = haveOCB; | |||
if (haveIt != null) { | |||
return haveIt.booleanValue(); | |||
} | |||
try { | |||
Cipher c = Cipher.getInstance("AES/OCB/NoPadding"); //$NON-NLS-1$ | |||
if (c == null) { | |||
haveOCB = Boolean.FALSE; | |||
return false; | |||
} | |||
} catch (NoClassDefFoundError | Exception e) { | |||
haveOCB = Boolean.FALSE; | |||
return false; | |||
} | |||
haveOCB = Boolean.TRUE; | |||
return true; | |||
} | |||
private static class TestData { | |||
final String name; | |||
final boolean encrypted; | |||
final boolean keyValue; | |||
TestData(String name, boolean encrypted, boolean keyValue) { | |||
this.name = name; | |||
this.encrypted = encrypted; | |||
this.keyValue = keyValue; | |||
} | |||
@Override | |||
public String toString() { | |||
return name; | |||
} | |||
} | |||
@Parameters(name = "{0}") | |||
public static TestData[] initTestData() { | |||
return new TestData[] { | |||
new TestData("AFDA8EA10E185ACF8C0D0F8885A0EF61A72ECB11", false, false), | |||
new TestData("2FB05DBB70FC07CB84C13431F640CA6CEA1DBF8A", false, true), | |||
new TestData("66CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9", true, true), | |||
new TestData("F727FAB884DA3BD402B6E0F5472E108D21033124", true, true), | |||
new TestData("faked", false, true) }; | |||
} | |||
private static byte[] readTestKey(String filename) throws Exception { | |||
try (InputStream in = new BufferedInputStream( | |||
SecretKeysTest.class.getResourceAsStream(filename))) { | |||
return SecretKeys.keyFromNameValueFormat(in); | |||
} | |||
} | |||
private static PGPPublicKey readAsc(InputStream in) | |||
throws IOException, PGPException { | |||
PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection( | |||
PGPUtil.getDecoderStream(in), new JcaKeyFingerprintCalculator()); | |||
Iterator<PGPPublicKeyRing> keyRings = pgpPub.getKeyRings(); | |||
while (keyRings.hasNext()) { | |||
PGPPublicKeyRing keyRing = keyRings.next(); | |||
Iterator<PGPPublicKey> keys = keyRing.getPublicKeys(); | |||
if (keys.hasNext()) { | |||
return keys.next(); | |||
} | |||
} | |||
return null; | |||
} | |||
// Injected by JUnit | |||
@Parameter | |||
public TestData data; | |||
@Test | |||
public void testKeyRead() throws Exception { | |||
if (data.keyValue) { | |||
byte[] bytes = readTestKey(data.name + ".key"); | |||
assertEquals('(', bytes[0]); | |||
assertEquals(')', bytes[bytes.length - 1]); | |||
} | |||
try (InputStream pubIn = this.getClass() | |||
.getResourceAsStream(data.name + ".asc")) { | |||
if (pubIn != null) { | |||
PGPPublicKey publicKey = readAsc(pubIn); | |||
// Do a full test trying to load the secret key. | |||
PGPDigestCalculatorProvider calculatorProvider = new JcaPGPDigestCalculatorProviderBuilder() | |||
.build(); | |||
try (InputStream in = new BufferedInputStream(this.getClass() | |||
.getResourceAsStream(data.name + ".key"))) { | |||
PGPSecretKey secretKey = SecretKeys.readSecretKey(in, | |||
calculatorProvider, | |||
data.encrypted ? () -> "nonsense".toCharArray() | |||
: null, | |||
publicKey); | |||
assertNotNull(secretKey); | |||
} catch (PGPException e) { | |||
// Currently we may not be able to load OCB-encrypted keys. | |||
assertTrue(e.getMessage().contains("OCB")); | |||
assertTrue(data.encrypted); | |||
assertFalse(ocbAvailable()); | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -8,22 +8,30 @@ Bundle-Vendor: %Bundle-Vendor | |||
Bundle-Localization: plugin | |||
Bundle-Version: 6.0.0.qualifier | |||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8 | |||
Import-Package: org.bouncycastle.bcpg;version="[1.65.0,2.0.0)", | |||
Import-Package: org.bouncycastle.asn1;version="[1.65.0,2.0.0)", | |||
org.bouncycastle.asn1.cryptlib;version="[1.65.0,2.0.0)", | |||
org.bouncycastle.asn1.x9;version="[1.65.0,2.0.0)", | |||
org.bouncycastle.bcpg;version="[1.65.0,2.0.0)", | |||
org.bouncycastle.bcpg.sig;version="[1.65.0,2.0.0)", | |||
org.bouncycastle.crypto.ec;version="[1.65.0,2.0.0)", | |||
org.bouncycastle.gpg;version="[1.65.0,2.0.0)", | |||
org.bouncycastle.gpg.keybox;version="[1.65.0,2.0.0)", | |||
org.bouncycastle.gpg.keybox.jcajce;version="[1.65.0,2.0.0)", | |||
org.bouncycastle.jcajce.interfaces;version="[1.65.0,2.0.0)", | |||
org.bouncycastle.jcajce.util;version="[1.65.0,2.0.0)", | |||
org.bouncycastle.jce.provider;version="[1.65.0,2.0.0)", | |||
org.bouncycastle.math.ec;version="[1.65.0,2.0.0)", | |||
org.bouncycastle.math.field;version="[1.65.0,2.0.0)", | |||
org.bouncycastle.openpgp;version="[1.65.0,2.0.0)", | |||
org.bouncycastle.openpgp.jcajce;version="[1.65.0,2.0.0)", | |||
org.bouncycastle.openpgp.operator;version="[1.65.0,2.0.0)", | |||
org.bouncycastle.openpgp.operator.jcajce;version="[1.65.0,2.0.0)", | |||
org.bouncycastle.util;version="[1.65.0,2.0.0)", | |||
org.bouncycastle.util.encoders;version="[1.65.0,2.0.0)", | |||
org.bouncycastle.util.io;version="[1.65.0,2.0.0)", | |||
org.eclipse.jgit.annotations;version="[6.0.0,6.1.0)", | |||
org.eclipse.jgit.api.errors;version="[6.0.0,6.1.0)", | |||
org.eclipse.jgit.errors;version="[6.0.0,6.1.0)", | |||
org.eclipse.jgit.lib;version="[6.0.0,6.1.0)", | |||
org.eclipse.jgit.nls;version="[6.0.0,6.1.0)", | |||
org.eclipse.jgit.transport;version="[6.0.0,6.1.0)", | |||
org.eclipse.jgit.util;version="[6.0.0,6.1.0)", | |||
org.slf4j;version="[1.7.0,2.0.0)" | |||
Export-Package: org.eclipse.jgit.gpg.bc.internal;version="6.0.0"; | |||
x-friends:="org.eclipse.jgit.gpg.bc.test" | |||
Export-Package: org.eclipse.jgit.gpg.bc;version="6.0.0", | |||
org.eclipse.jgit.gpg.bc.internal;version="6.0.0";x-friends:="org.eclipse.jgit.gpg.bc.test", | |||
org.eclipse.jgit.gpg.bc.internal.keys;version="6.0.0";x-friends:="org.eclipse.jgit.gpg.bc.test" |
@@ -5,3 +5,4 @@ Bundle-SymbolicName: org.eclipse.jgit.gpg.bc.source | |||
Bundle-Vendor: Eclipse.org - JGit | |||
Bundle-Version: 6.0.0.qualifier | |||
Eclipse-SourceBundle: org.eclipse.jgit.gpg.bc;version="6.0.0.qualifier";roots="." | |||
@@ -11,7 +11,7 @@ | |||
margin: 0.25in 0.5in 0.25in 0.5in; | |||
tab-interval: 0.5in; | |||
} | |||
p { | |||
p { | |||
margin-left: auto; | |||
margin-top: 0.5em; | |||
margin-bottom: 0.5em; | |||
@@ -36,60 +36,53 @@ | |||
<p>Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors. </p> | |||
<p>All rights reserved.</p> | |||
<p>Redistribution and use in source and binary forms, with or without modification, | |||
<p>Redistribution and use in source and binary forms, with or without modification, | |||
are permitted provided that the following conditions are met: | |||
<ul><li>Redistributions of source code must retain the above copyright notice, | |||
<ul><li>Redistributions of source code must retain the above copyright notice, | |||
this list of conditions and the following disclaimer. </li> | |||
<li>Redistributions in binary form must reproduce the above copyright notice, | |||
this list of conditions and the following disclaimer in the documentation | |||
<li>Redistributions in binary form must reproduce the above copyright notice, | |||
this list of conditions and the following disclaimer in the documentation | |||
and/or other materials provided with the distribution. </li> | |||
<li>Neither the name of the Eclipse Foundation, Inc. nor the names of its | |||
contributors may be used to endorse or promote products derived from | |||
<li>Neither the name of the Eclipse Foundation, Inc. nor the names of its | |||
contributors may be used to endorse or promote products derived from | |||
this software without specific prior written permission. </li></ul> | |||
</p> | |||
<p>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |||
IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, | |||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | |||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||
<p>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |||
IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, | |||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | |||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||
POSSIBILITY OF SUCH DAMAGE.</p> | |||
<hr> | |||
<p><b>SHA-1 UbcCheck - MIT</b></p> | |||
<p><b>org.eclipse.jgit.gpg.bc.internal.keys.SExprParser - MIT</b></p> | |||
<p>Copyright (c) 2017:</p> | |||
<div class="ubc-name"> | |||
Marc Stevens | |||
Cryptology Group | |||
Centrum Wiskunde & Informatica | |||
P.O. Box 94079, 1090 GB Amsterdam, Netherlands | |||
marc@marc-stevens.nl | |||
</div> | |||
<div class="ubc-name"> | |||
Dan Shumow | |||
Microsoft Research | |||
danshu@microsoft.com | |||
</div> | |||
<p>Permission is hereby granted, free of charge, to any person obtaining a copy | |||
of this software and associated documentation files (the "Software"), to deal | |||
in the Software without restriction, including without limitation the rights | |||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
copies of the Software, and to permit persons to whom the Software is | |||
furnished to do so, subject to the following conditions: | |||
<p>Copyright (c) 2000-2021 The Legion of the Bouncy Castle Inc. | |||
(<a href="https://www.bouncycastle.org">https://www.bouncycastle.org</a>)</p> | |||
<p> | |||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software | |||
and associated documentation files (the "Software"), to deal in the Software without restriction, | |||
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, | |||
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, | |||
subject to the following conditions: | |||
</p> | |||
<p> | |||
The above copyright notice and this permission notice shall be included in all copies or substantial | |||
portions of the Software. | |||
</p> | |||
<p> | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, | |||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR | |||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | |||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |||
DEALINGS IN THE SOFTWARE. | |||
</p> | |||
<ul><li>The above copyright notice and this permission notice shall be included | |||
in all copies or substantial portions of the Software.</li></ul> | |||
<p>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
SOFTWARE.</p> | |||
</body> | |||
@@ -0,0 +1 @@ | |||
org.eclipse.jgit.gpg.bc.internal.BouncyCastleGpgSignatureVerifierFactory |
@@ -1,11 +1,36 @@ | |||
corrupt25519Key=Ed25519/Curve25519 public key has wrong length: {0} | |||
credentialPassphrase=Passphrase | |||
gpgFailedToParseSecretKey=Failed to parse secret key file in directory: {0}. Is the entered passphrase correct? | |||
cryptCipherError=Cannot create cipher to decrypt: {0} | |||
cryptWrongDecryptedLength=Decrypted key has wrong length; expected {0} bytes, got only {1} bytes | |||
gpgFailedToParseSecretKey=Failed to parse secret key file {0}. Is the entered passphrase correct? | |||
gpgNoCredentialsProvider=missing credentials provider | |||
gpgNoKeygrip=Cannot find key {0}: cannot determine key grip | |||
gpgNoKeyring=neither pubring.kbx nor secring.gpg files found | |||
gpgNoKeyInLegacySecring=no matching secret key found in legacy secring.gpg for key or user id: {0} | |||
gpgNoPublicKeyFound=Unable to find a public-key with key or user id: {0} | |||
gpgNoSecretKeyForPublicKey=unable to find associated secret key for public key: {0} | |||
gpgNoSuchAlgorithm=Cannot decrypt encrypted secret key: encryption algorithm {0} is not available | |||
gpgNotASigningKey=Secret key ({0}) is not suitable for signing | |||
gpgKeyInfo=GPG Key (fingerprint {0}) | |||
gpgSigningCancelled=Signing was cancelled | |||
nonSignatureError=Signature does not decode into a signature object | |||
secretKeyTooShort=Secret key file corrupt; only {0} bytes read | |||
sexprHexNotClosed=Hex number in s-expression not closed | |||
sexprHexOdd=Hex number in s-expression has an odd number of digits | |||
sexprStringInvalidEscape=Invalid escape {0} in s-expression | |||
sexprStringInvalidEscapeAtEnd=Invalid s-expression: quoted string ends with escape character | |||
sexprStringInvalidHexEscape=Invalid hex escape in s-expression | |||
sexprStringInvalidOctalEscape=Invalid octal escape in s-expression | |||
sexprStringNotClosed=String in s-expression not closed | |||
sexprUnhandled=Unhandled token {0} in s-expression | |||
signatureInconsistent=Inconsistent signature; key ID {0} does not match issuer fingerprint {1} | |||
signatureKeyLookupError=Error occurred while looking for public key | |||
signatureNoKeyInfo=No way to determine a public key from the signature | |||
signatureNoPublicKey=No public key found to verify the signature | |||
signatureParseError=Signature cannot be parsed | |||
signatureVerificationError=Signature verification failed | |||
unableToSignCommitNoSecretKey=Unable to sign commit. Signing key not available. | |||
uncompressed25519Key=Cannot handle ed25519 public key with uncompressed data: {0} | |||
unknownCurve=Unknown curve {0} | |||
unknownCurveParameters=Curve {0} does not have a prime field | |||
unknownKeyType=Unknown key type {0} |
@@ -0,0 +1,34 @@ | |||
/* | |||
* Copyright (C) 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others | |||
* | |||
* This program and the accompanying materials are made available under the | |||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||
* https://www.eclipse.org/org/documents/edl-v10.php. | |||
* | |||
* SPDX-License-Identifier: BSD-3-Clause | |||
*/ | |||
package org.eclipse.jgit.gpg.bc; | |||
import org.eclipse.jgit.gpg.bc.internal.BouncyCastleGpgSigner; | |||
import org.eclipse.jgit.lib.GpgSigner; | |||
/** | |||
* Factory for creating a {@link GpgSigner} based on Bouncy Castle. | |||
* | |||
* @since 5.11 | |||
*/ | |||
public final class BouncyCastleGpgSignerFactory { | |||
private BouncyCastleGpgSignerFactory() { | |||
// No instantiation | |||
} | |||
/** | |||
* Creates a new {@link GpgSigner}. | |||
* | |||
* @return the {@link GpgSigner} | |||
*/ | |||
public static GpgSigner create() { | |||
return new BouncyCastleGpgSigner(); | |||
} | |||
} |
@@ -1,3 +1,12 @@ | |||
/* | |||
* Copyright (C) 2018, 2021 Salesforce and others | |||
* | |||
* This program and the accompanying materials are made available under the | |||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||
* https://www.eclipse.org/org/documents/edl-v10.php. | |||
* | |||
* SPDX-License-Identifier: BSD-3-Clause | |||
*/ | |||
package org.eclipse.jgit.gpg.bc.internal; | |||
import org.eclipse.jgit.nls.NLS; | |||
@@ -18,16 +27,41 @@ public final class BCText extends TranslationBundle { | |||
} | |||
// @formatter:off | |||
/***/ public String corrupt25519Key; | |||
/***/ public String credentialPassphrase; | |||
/***/ public String cryptCipherError; | |||
/***/ public String cryptWrongDecryptedLength; | |||
/***/ public String gpgFailedToParseSecretKey; | |||
/***/ public String gpgNoCredentialsProvider; | |||
/***/ public String gpgNoKeygrip; | |||
/***/ public String gpgNoKeyring; | |||
/***/ public String gpgNoKeyInLegacySecring; | |||
/***/ public String gpgNoPublicKeyFound; | |||
/***/ public String gpgNoSecretKeyForPublicKey; | |||
/***/ public String gpgNoSuchAlgorithm; | |||
/***/ public String gpgNotASigningKey; | |||
/***/ public String gpgKeyInfo; | |||
/***/ public String gpgSigningCancelled; | |||
/***/ public String nonSignatureError; | |||
/***/ public String secretKeyTooShort; | |||
/***/ public String sexprHexNotClosed; | |||
/***/ public String sexprHexOdd; | |||
/***/ public String sexprStringInvalidEscape; | |||
/***/ public String sexprStringInvalidEscapeAtEnd; | |||
/***/ public String sexprStringInvalidHexEscape; | |||
/***/ public String sexprStringInvalidOctalEscape; | |||
/***/ public String sexprStringNotClosed; | |||
/***/ public String sexprUnhandled; | |||
/***/ public String signatureInconsistent; | |||
/***/ public String signatureKeyLookupError; | |||
/***/ public String signatureNoKeyInfo; | |||
/***/ public String signatureNoPublicKey; | |||
/***/ public String signatureParseError; | |||
/***/ public String signatureVerificationError; | |||
/***/ public String unableToSignCommitNoSecretKey; | |||
/***/ public String uncompressed25519Key; | |||
/***/ public String unknownCurve; | |||
/***/ public String unknownCurveParameters; | |||
/***/ public String unknownKeyType; | |||
} |
@@ -1,5 +1,5 @@ | |||
/* | |||
* Copyright (C) 2018, 2020 Salesforce and others | |||
* Copyright (C) 2018, 2021 Salesforce and others | |||
* | |||
* This program and the accompanying materials are made available under the | |||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||
@@ -14,25 +14,22 @@ import static java.nio.file.Files.newInputStream; | |||
import java.io.BufferedInputStream; | |||
import java.io.File; | |||
import java.io.FileNotFoundException; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.net.URISyntaxException; | |||
import java.nio.file.DirectoryStream; | |||
import java.nio.file.Files; | |||
import java.nio.file.InvalidPathException; | |||
import java.nio.file.NoSuchFileException; | |||
import java.nio.file.Path; | |||
import java.nio.file.Paths; | |||
import java.security.NoSuchAlgorithmException; | |||
import java.security.NoSuchProviderException; | |||
import java.text.MessageFormat; | |||
import java.util.ArrayList; | |||
import java.util.Iterator; | |||
import java.util.List; | |||
import java.util.Locale; | |||
import java.util.stream.Collectors; | |||
import java.util.stream.Stream; | |||
import org.bouncycastle.gpg.SExprParser; | |||
import org.bouncycastle.gpg.keybox.BlobType; | |||
import org.bouncycastle.gpg.keybox.KeyBlob; | |||
import org.bouncycastle.gpg.keybox.KeyBox; | |||
@@ -50,15 +47,15 @@ import org.bouncycastle.openpgp.PGPSecretKeyRing; | |||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; | |||
import org.bouncycastle.openpgp.PGPSignature; | |||
import org.bouncycastle.openpgp.PGPUtil; | |||
import org.bouncycastle.openpgp.operator.PBEProtectionRemoverFactory; | |||
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; | |||
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; | |||
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; | |||
import org.bouncycastle.openpgp.operator.jcajce.JcePBEProtectionRemoverFactory; | |||
import org.bouncycastle.util.encoders.Hex; | |||
import org.eclipse.jgit.annotations.NonNull; | |||
import org.eclipse.jgit.api.errors.CanceledException; | |||
import org.eclipse.jgit.errors.UnsupportedCredentialItem; | |||
import org.eclipse.jgit.gpg.bc.internal.keys.KeyGrip; | |||
import org.eclipse.jgit.gpg.bc.internal.keys.SecretKeys; | |||
import org.eclipse.jgit.util.FS; | |||
import org.eclipse.jgit.util.StringUtils; | |||
import org.eclipse.jgit.util.SystemReader; | |||
@@ -78,17 +75,10 @@ public class BouncyCastleGpgKeyLocator { | |||
} | |||
/** Thrown if we try to read an encrypted private key without password. */ | |||
private static class EncryptedPgpKeyException extends RuntimeException { | |||
private static final long serialVersionUID = 1L; | |||
} | |||
private static final Logger log = LoggerFactory | |||
.getLogger(BouncyCastleGpgKeyLocator.class); | |||
private static final Path GPG_DIRECTORY = findGpgDirectory(); | |||
static final Path GPG_DIRECTORY = findGpgDirectory(); | |||
private static final Path USER_KEYBOX_PATH = GPG_DIRECTORY | |||
.resolve("pubring.kbx"); //$NON-NLS-1$ | |||
@@ -155,16 +145,13 @@ public class BouncyCastleGpgKeyLocator { | |||
private PGPSecretKey attemptParseSecretKey(Path keyFile, | |||
PGPDigestCalculatorProvider calculatorProvider, | |||
PBEProtectionRemoverFactory passphraseProvider, | |||
PGPPublicKey publicKey) { | |||
SecretKeys.PassphraseSupplier passphraseSupplier, | |||
PGPPublicKey publicKey) | |||
throws IOException, PGPException, CanceledException, | |||
UnsupportedCredentialItem, URISyntaxException { | |||
try (InputStream in = newInputStream(keyFile)) { | |||
return new SExprParser(calculatorProvider).parseSecretKey( | |||
new BufferedInputStream(in), passphraseProvider, publicKey); | |||
} catch (IOException | PGPException | ClassCastException e) { | |||
if (log.isDebugEnabled()) | |||
log.debug("Ignoring unreadable file '{}': {}", keyFile, //$NON-NLS-1$ | |||
e.getMessage(), e); | |||
return null; | |||
return SecretKeys.readSecretKey(in, calculatorProvider, | |||
passphraseSupplier, publicKey); | |||
} | |||
} | |||
@@ -219,33 +206,60 @@ public class BouncyCastleGpgKeyLocator { | |||
int stop = toMatch.indexOf('>'); | |||
return begin >= 0 && end > begin + 1 && stop > 0 | |||
&& userId.substring(begin + 1, end) | |||
.equals(toMatch.substring(0, stop)); | |||
.equalsIgnoreCase(toMatch.substring(0, stop)); | |||
} | |||
case '@': { | |||
int begin = userId.indexOf('<'); | |||
int end = userId.indexOf('>', begin + 1); | |||
return begin >= 0 && end > begin + 1 | |||
&& userId.substring(begin + 1, end).contains(toMatch); | |||
&& containsIgnoreCase(userId.substring(begin + 1, end), | |||
toMatch); | |||
} | |||
default: | |||
if (toMatch.trim().isEmpty()) { | |||
return false; | |||
} | |||
return userId.toLowerCase(Locale.ROOT) | |||
.contains(toMatch.toLowerCase(Locale.ROOT)); | |||
return containsIgnoreCase(userId, toMatch); | |||
} | |||
} | |||
private static boolean containsIgnoreCase(String a, String b) { | |||
int alength = a.length(); | |||
int blength = b.length(); | |||
for (int i = 0; i + blength <= alength; i++) { | |||
if (a.regionMatches(true, i, b, 0, blength)) { | |||
return true; | |||
} | |||
} | |||
return false; | |||
} | |||
private String toFingerprint(String keyId) { | |||
private static String toFingerprint(String keyId) { | |||
if (keyId.startsWith("0x")) { //$NON-NLS-1$ | |||
return keyId.substring(2); | |||
} | |||
return keyId; | |||
} | |||
private PGPPublicKey findPublicKeyByKeyId(KeyBlob keyBlob) | |||
static PGPPublicKey findPublicKey(String fingerprint, String keySpec) | |||
throws IOException, PGPException { | |||
PGPPublicKey result = findPublicKeyInPubring(USER_PGP_PUBRING_FILE, | |||
fingerprint, keySpec); | |||
if (result == null && exists(USER_KEYBOX_PATH)) { | |||
try { | |||
result = findPublicKeyInKeyBox(USER_KEYBOX_PATH, fingerprint, | |||
keySpec); | |||
} catch (NoSuchAlgorithmException | NoSuchProviderException | |||
| IOException | NoOpenPgpKeyException e) { | |||
log.error(e.getMessage(), e); | |||
} | |||
} | |||
return result; | |||
} | |||
private static PGPPublicKey findPublicKeyByKeyId(KeyBlob keyBlob, | |||
String keyId) | |||
throws IOException { | |||
String keyId = toFingerprint(signingKey).toLowerCase(Locale.ROOT); | |||
if (keyId.isEmpty()) { | |||
return null; | |||
} | |||
@@ -259,10 +273,11 @@ public class BouncyCastleGpgKeyLocator { | |||
return null; | |||
} | |||
private PGPPublicKey findPublicKeyByUserId(KeyBlob keyBlob) | |||
private static PGPPublicKey findPublicKeyByUserId(KeyBlob keyBlob, | |||
String keySpec) | |||
throws IOException { | |||
for (UserID userID : keyBlob.getUserIds()) { | |||
if (containsSigningKey(userID.getUserIDAsString(), signingKey)) { | |||
if (containsSigningKey(userID.getUserIDAsString(), keySpec)) { | |||
return getSigningPublicKey(keyBlob); | |||
} | |||
} | |||
@@ -274,6 +289,10 @@ public class BouncyCastleGpgKeyLocator { | |||
* | |||
* @param keyboxFile | |||
* the KeyBox file | |||
* @param keyId | |||
* to look for, may be null | |||
* @param keySpec | |||
* to look for | |||
* @return publicKey the public key (maybe <code>null</code>) | |||
* @throws IOException | |||
* in case of problems reading the file | |||
@@ -282,19 +301,22 @@ public class BouncyCastleGpgKeyLocator { | |||
* @throws NoOpenPgpKeyException | |||
* if the file does not contain any OpenPGP key | |||
*/ | |||
private PGPPublicKey findPublicKeyInKeyBox(Path keyboxFile) | |||
private static PGPPublicKey findPublicKeyInKeyBox(Path keyboxFile, | |||
String keyId, String keySpec) | |||
throws IOException, NoSuchAlgorithmException, | |||
NoSuchProviderException, NoOpenPgpKeyException { | |||
KeyBox keyBox = readKeyBoxFile(keyboxFile); | |||
String id = keyId != null ? keyId | |||
: toFingerprint(keySpec).toLowerCase(Locale.ROOT); | |||
boolean hasOpenPgpKey = false; | |||
for (KeyBlob keyBlob : keyBox.getKeyBlobs()) { | |||
if (keyBlob.getType() == BlobType.OPEN_PGP_BLOB) { | |||
hasOpenPgpKey = true; | |||
PGPPublicKey key = findPublicKeyByKeyId(keyBlob); | |||
PGPPublicKey key = findPublicKeyByKeyId(keyBlob, id); | |||
if (key != null) { | |||
return key; | |||
} | |||
key = findPublicKeyByUserId(keyBlob); | |||
key = findPublicKeyByUserId(keyBlob, keySpec); | |||
if (key != null) { | |||
return key; | |||
} | |||
@@ -338,7 +360,8 @@ public class BouncyCastleGpgKeyLocator { | |||
// pubring.gpg also try secring.gpg to find the secret key. | |||
if (exists(USER_KEYBOX_PATH)) { | |||
try { | |||
publicKey = findPublicKeyInKeyBox(USER_KEYBOX_PATH); | |||
publicKey = findPublicKeyInKeyBox(USER_KEYBOX_PATH, null, | |||
signingKey); | |||
if (publicKey != null) { | |||
key = findSecretKeyForKeyBoxPublicKey(publicKey, | |||
USER_KEYBOX_PATH); | |||
@@ -361,7 +384,8 @@ public class BouncyCastleGpgKeyLocator { | |||
} | |||
} | |||
if (exists(USER_PGP_PUBRING_FILE)) { | |||
publicKey = findPublicKeyInPubring(USER_PGP_PUBRING_FILE); | |||
publicKey = findPublicKeyInPubring(USER_PGP_PUBRING_FILE, null, | |||
signingKey); | |||
if (publicKey != null) { | |||
// GPG < 2.1 may have both; the agent using the directory | |||
// and gpg using secring.gpg. GPG >= 2.1 delegates all | |||
@@ -433,67 +457,59 @@ public class BouncyCastleGpgKeyLocator { | |||
PGPPublicKey publicKey, Path userKeyboxPath) | |||
throws PGPException, CanceledException, UnsupportedCredentialItem, | |||
URISyntaxException { | |||
/* | |||
* this is somewhat brute-force but there doesn't seem to be another | |||
* way; we have to walk all private key files we find and try to open | |||
* them | |||
*/ | |||
PGPDigestCalculatorProvider calculatorProvider = new JcaPGPDigestCalculatorProviderBuilder() | |||
.build(); | |||
try (Stream<Path> keyFiles = Files.walk(USER_SECRET_KEY_DIR)) { | |||
List<Path> allPaths = keyFiles.filter(Files::isRegularFile) | |||
.collect(Collectors.toCollection(ArrayList::new)); | |||
if (allPaths.isEmpty()) { | |||
return null; | |||
byte[] keyGrip = null; | |||
try { | |||
keyGrip = KeyGrip.getKeyGrip(publicKey); | |||
} catch (PGPException e) { | |||
throw new PGPException( | |||
MessageFormat.format(BCText.get().gpgNoKeygrip, | |||
Hex.toHexString(publicKey.getFingerprint())), | |||
e); | |||
} | |||
String filename = Hex.toHexString(keyGrip).toUpperCase(Locale.ROOT) | |||
+ ".key"; //$NON-NLS-1$ | |||
Path keyFile = USER_SECRET_KEY_DIR.resolve(filename); | |||
if (!Files.exists(keyFile)) { | |||
return null; | |||
} | |||
boolean clearPrompt = false; | |||
try { | |||
PGPDigestCalculatorProvider calculatorProvider = new JcaPGPDigestCalculatorProviderBuilder() | |||
.build(); | |||
clearPrompt = true; | |||
PGPSecretKey secretKey = null; | |||
try { | |||
secretKey = attemptParseSecretKey(keyFile, calculatorProvider, | |||
() -> passphrasePrompt.getPassphrase( | |||
publicKey.getFingerprint(), userKeyboxPath), | |||
publicKey); | |||
} catch (PGPException e) { | |||
throw new PGPException(MessageFormat.format( | |||
BCText.get().gpgFailedToParseSecretKey, | |||
keyFile.toAbsolutePath()), e); | |||
} | |||
PBEProtectionRemoverFactory passphraseProvider = p -> { | |||
throw new EncryptedPgpKeyException(); | |||
}; | |||
for (int attempts = 0; attempts < 2; attempts++) { | |||
// Second pass will traverse only the encrypted keys with a real | |||
// passphrase provider. | |||
Iterator<Path> pathIterator = allPaths.iterator(); | |||
while (pathIterator.hasNext()) { | |||
Path keyFile = pathIterator.next(); | |||
try { | |||
PGPSecretKey secretKey = attemptParseSecretKey(keyFile, | |||
calculatorProvider, passphraseProvider, | |||
publicKey); | |||
pathIterator.remove(); | |||
if (secretKey != null) { | |||
if (!secretKey.isSigningKey()) { | |||
throw new PGPException(MessageFormat.format( | |||
BCText.get().gpgNotASigningKey, | |||
signingKey)); | |||
} | |||
return new BouncyCastleGpgKey(secretKey, | |||
userKeyboxPath); | |||
} | |||
} catch (EncryptedPgpKeyException e) { | |||
// Ignore; we'll try again. | |||
} | |||
} | |||
if (attempts > 0 || allPaths.isEmpty()) { | |||
break; | |||
if (secretKey != null) { | |||
if (!secretKey.isSigningKey()) { | |||
throw new PGPException(MessageFormat.format( | |||
BCText.get().gpgNotASigningKey, signingKey)); | |||
} | |||
// allPaths contains only the encrypted keys now. | |||
passphraseProvider = new JcePBEProtectionRemoverFactory( | |||
passphrasePrompt.getPassphrase( | |||
publicKey.getFingerprint(), userKeyboxPath)); | |||
clearPrompt = false; | |||
return new BouncyCastleGpgKey(secretKey, userKeyboxPath); | |||
} | |||
passphrasePrompt.clear(); | |||
return null; | |||
} catch (RuntimeException e) { | |||
passphrasePrompt.clear(); | |||
throw e; | |||
} catch (FileNotFoundException | NoSuchFileException e) { | |||
clearPrompt = false; | |||
return null; | |||
} catch (IOException e) { | |||
passphrasePrompt.clear(); | |||
throw new PGPException(MessageFormat.format( | |||
BCText.get().gpgFailedToParseSecretKey, | |||
USER_SECRET_KEY_DIR.toAbsolutePath()), e); | |||
keyFile.toAbsolutePath()), e); | |||
} finally { | |||
if (clearPrompt) { | |||
passphrasePrompt.clear(); | |||
} | |||
} | |||
} | |||
@@ -551,6 +567,11 @@ public class BouncyCastleGpgKeyLocator { | |||
* Return the first public key matching the key id ({@link #signingKey}. | |||
* | |||
* @param pubringFile | |||
* to search | |||
* @param keyId | |||
* to look for, may be null | |||
* @param keySpec | |||
* to look for | |||
* | |||
* @return the PGP public key, or {@code null} if none found | |||
* @throws IOException | |||
@@ -558,14 +579,16 @@ public class BouncyCastleGpgKeyLocator { | |||
* @throws PGPException | |||
* on BouncyCastle errors | |||
*/ | |||
private PGPPublicKey findPublicKeyInPubring(Path pubringFile) | |||
private static PGPPublicKey findPublicKeyInPubring(Path pubringFile, | |||
String keyId, String keySpec) | |||
throws IOException, PGPException { | |||
try (InputStream in = newInputStream(pubringFile)) { | |||
PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection( | |||
new BufferedInputStream(in), | |||
new JcaKeyFingerprintCalculator()); | |||
String keyId = toFingerprint(signingKey).toLowerCase(Locale.ROOT); | |||
String id = keyId != null ? keyId | |||
: toFingerprint(keySpec).toLowerCase(Locale.ROOT); | |||
Iterator<PGPPublicKeyRing> keyrings = pgpPub.getKeyRings(); | |||
while (keyrings.hasNext()) { | |||
PGPPublicKeyRing keyRing = keyrings.next(); | |||
@@ -575,30 +598,33 @@ public class BouncyCastleGpgKeyLocator { | |||
// try key id | |||
String fingerprint = Hex.toHexString(key.getFingerprint()) | |||
.toLowerCase(Locale.ROOT); | |||
if (fingerprint.endsWith(keyId)) { | |||
if (fingerprint.endsWith(id)) { | |||
return key; | |||
} | |||
// try user id | |||
Iterator<String> userIDs = key.getUserIDs(); | |||
while (userIDs.hasNext()) { | |||
String userId = userIDs.next(); | |||
if (containsSigningKey(userId, signingKey)) { | |||
if (containsSigningKey(userId, keySpec)) { | |||
return key; | |||
} | |||
} | |||
} | |||
} | |||
} catch (FileNotFoundException | NoSuchFileException e) { | |||
// Ignore and return null | |||
} | |||
return null; | |||
} | |||
private PGPPublicKey getPublicKey(KeyBlob blob, byte[] fingerprint) | |||
private static PGPPublicKey getPublicKey(KeyBlob blob, byte[] fingerprint) | |||
throws IOException { | |||
return ((PublicKeyRingBlob) blob).getPGPPublicKeyRing() | |||
.getPublicKey(fingerprint); | |||
} | |||
private PGPPublicKey getSigningPublicKey(KeyBlob blob) throws IOException { | |||
private static PGPPublicKey getSigningPublicKey(KeyBlob blob) | |||
throws IOException { | |||
PGPPublicKey masterKey = null; | |||
Iterator<PGPPublicKey> keys = ((PublicKeyRingBlob) blob) | |||
.getPGPPublicKeyRing().getPublicKeys(); | |||
@@ -618,7 +644,7 @@ public class BouncyCastleGpgKeyLocator { | |||
return masterKey; | |||
} | |||
private boolean isSigningKey(PGPPublicKey key) { | |||
private static boolean isSigningKey(PGPPublicKey key) { | |||
Iterator signatures = key.getSignatures(); | |||
while (signatures.hasNext()) { | |||
PGPSignature sig = (PGPSignature) signatures.next(); | |||
@@ -630,7 +656,7 @@ public class BouncyCastleGpgKeyLocator { | |||
return false; | |||
} | |||
private KeyBox readKeyBoxFile(Path keyboxFile) throws IOException, | |||
private static KeyBox readKeyBoxFile(Path keyboxFile) throws IOException, | |||
NoSuchAlgorithmException, NoSuchProviderException, | |||
NoOpenPgpKeyException { | |||
if (keyboxFile.toFile().length() == 0) { |
@@ -17,8 +17,8 @@ import org.bouncycastle.openpgp.PGPException; | |||
import org.bouncycastle.util.encoders.Hex; | |||
import org.eclipse.jgit.api.errors.CanceledException; | |||
import org.eclipse.jgit.errors.UnsupportedCredentialItem; | |||
import org.eclipse.jgit.transport.CredentialItem.CharArrayType; | |||
import org.eclipse.jgit.transport.CredentialItem.InformationalMessage; | |||
import org.eclipse.jgit.transport.CredentialItem.Password; | |||
import org.eclipse.jgit.transport.CredentialsProvider; | |||
import org.eclipse.jgit.transport.URIish; | |||
@@ -31,7 +31,7 @@ import org.eclipse.jgit.transport.URIish; | |||
*/ | |||
class BouncyCastleGpgKeyPassphrasePrompt implements AutoCloseable { | |||
private CharArrayType passphrase; | |||
private Password passphrase; | |||
private CredentialsProvider credentialsProvider; | |||
@@ -78,8 +78,7 @@ class BouncyCastleGpgKeyPassphrasePrompt implements AutoCloseable { | |||
throws PGPException, CanceledException, UnsupportedCredentialItem, | |||
URISyntaxException { | |||
if (passphrase == null) { | |||
passphrase = new CharArrayType(BCText.get().credentialPassphrase, | |||
true); | |||
passphrase = new Password(BCText.get().credentialPassphrase); | |||
} | |||
if (credentialsProvider == null) { |
@@ -0,0 +1,388 @@ | |||
/* | |||
* Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others | |||
* | |||
* This program and the accompanying materials are made available under the | |||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||
* https://www.eclipse.org/org/documents/edl-v10.php. | |||
* | |||
* SPDX-License-Identifier: BSD-3-Clause | |||
*/ | |||
package org.eclipse.jgit.gpg.bc.internal; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.security.Security; | |||
import java.text.MessageFormat; | |||
import java.time.Instant; | |||
import java.util.Arrays; | |||
import java.util.Date; | |||
import java.util.Iterator; | |||
import java.util.Locale; | |||
import org.bouncycastle.bcpg.sig.IssuerFingerprint; | |||
import org.bouncycastle.jce.provider.BouncyCastleProvider; | |||
import org.bouncycastle.openpgp.PGPCompressedData; | |||
import org.bouncycastle.openpgp.PGPException; | |||
import org.bouncycastle.openpgp.PGPPublicKey; | |||
import org.bouncycastle.openpgp.PGPSignature; | |||
import org.bouncycastle.openpgp.PGPSignatureList; | |||
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; | |||
import org.bouncycastle.openpgp.PGPUtil; | |||
import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory; | |||
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; | |||
import org.bouncycastle.util.encoders.Hex; | |||
import org.eclipse.jgit.annotations.NonNull; | |||
import org.eclipse.jgit.annotations.Nullable; | |||
import org.eclipse.jgit.api.errors.JGitInternalException; | |||
import org.eclipse.jgit.lib.GpgConfig; | |||
import org.eclipse.jgit.lib.GpgSignatureVerifier; | |||
import org.eclipse.jgit.revwalk.RevCommit; | |||
import org.eclipse.jgit.revwalk.RevObject; | |||
import org.eclipse.jgit.revwalk.RevTag; | |||
import org.eclipse.jgit.util.LRUMap; | |||
import org.eclipse.jgit.util.RawParseUtils; | |||
import org.eclipse.jgit.util.StringUtils; | |||
/** | |||
* A {@link GpgSignatureVerifier} to verify GPG signatures using BouncyCastle. | |||
*/ | |||
public class BouncyCastleGpgSignatureVerifier implements GpgSignatureVerifier { | |||
private static void registerBouncyCastleProviderIfNecessary() { | |||
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { | |||
Security.addProvider(new BouncyCastleProvider()); | |||
} | |||
} | |||
/** | |||
* Creates a new instance and registers the BouncyCastle security provider | |||
* if needed. | |||
*/ | |||
public BouncyCastleGpgSignatureVerifier() { | |||
registerBouncyCastleProviderIfNecessary(); | |||
} | |||
// To support more efficient signature verification of multiple objects we | |||
// cache public keys once found in a LRU cache. | |||
private static final Object NO_KEY = new Object(); | |||
private LRUMap<String, Object> byFingerprint = new LRUMap<>(16, 200); | |||
private LRUMap<String, Object> bySigner = new LRUMap<>(16, 200); | |||
@Override | |||
public String getName() { | |||
return "bc"; //$NON-NLS-1$ | |||
} | |||
@Override | |||
@Nullable | |||
public SignatureVerification verifySignature(@NonNull RevObject object, | |||
@NonNull GpgConfig config) throws IOException { | |||
if (object instanceof RevCommit) { | |||
RevCommit commit = (RevCommit) object; | |||
byte[] signatureData = commit.getRawGpgSignature(); | |||
if (signatureData == null) { | |||
return null; | |||
} | |||
byte[] raw = commit.getRawBuffer(); | |||
// Now remove the GPG signature | |||
byte[] header = { 'g', 'p', 'g', 's', 'i', 'g' }; | |||
int start = RawParseUtils.headerStart(header, raw, 0); | |||
if (start < 0) { | |||
return null; | |||
} | |||
int end = RawParseUtils.headerEnd(raw, start); | |||
// start is at the beginning of the header's content | |||
start -= header.length + 1; | |||
// end is on the terminating LF; we need to skip that, too | |||
if (end < raw.length) { | |||
end++; | |||
} | |||
byte[] data = new byte[raw.length - (end - start)]; | |||
System.arraycopy(raw, 0, data, 0, start); | |||
System.arraycopy(raw, end, data, start, raw.length - end); | |||
return verify(data, signatureData); | |||
} else if (object instanceof RevTag) { | |||
RevTag tag = (RevTag) object; | |||
byte[] signatureData = tag.getRawGpgSignature(); | |||
if (signatureData == null) { | |||
return null; | |||
} | |||
byte[] raw = tag.getRawBuffer(); | |||
// The signature is just tacked onto the end of the message, which | |||
// is last in the buffer. | |||
byte[] data = Arrays.copyOfRange(raw, 0, | |||
raw.length - signatureData.length); | |||
return verify(data, signatureData); | |||
} | |||
return null; | |||
} | |||
static PGPSignature parseSignature(InputStream in) | |||
throws IOException, PGPException { | |||
try (InputStream sigIn = PGPUtil.getDecoderStream(in)) { | |||
JcaPGPObjectFactory pgpFactory = new JcaPGPObjectFactory(sigIn); | |||
Object obj = pgpFactory.nextObject(); | |||
if (obj instanceof PGPCompressedData) { | |||
obj = new JcaPGPObjectFactory( | |||
((PGPCompressedData) obj).getDataStream()).nextObject(); | |||
} | |||
if (obj instanceof PGPSignatureList) { | |||
return ((PGPSignatureList) obj).get(0); | |||
} | |||
return null; | |||
} | |||
} | |||
@Override | |||
public SignatureVerification verify(byte[] data, byte[] signatureData) | |||
throws IOException { | |||
PGPSignature signature = null; | |||
String fingerprint = null; | |||
String signer = null; | |||
String keyId = null; | |||
try (InputStream sigIn = new ByteArrayInputStream(signatureData)) { | |||
signature = parseSignature(sigIn); | |||
if (signature != null) { | |||
// Try to figure out something to find the public key with. | |||
if (signature.hasSubpackets()) { | |||
PGPSignatureSubpacketVector packets = signature | |||
.getHashedSubPackets(); | |||
IssuerFingerprint fingerprintPacket = packets | |||
.getIssuerFingerprint(); | |||
if (fingerprintPacket != null) { | |||
fingerprint = Hex | |||
.toHexString(fingerprintPacket.getFingerprint()) | |||
.toLowerCase(Locale.ROOT); | |||
} | |||
signer = packets.getSignerUserID(); | |||
if (signer != null) { | |||
signer = BouncyCastleGpgSigner.extractSignerId(signer); | |||
} | |||
} | |||
keyId = Long.toUnsignedString(signature.getKeyID(), 16) | |||
.toLowerCase(Locale.ROOT); | |||
} else { | |||
throw new JGitInternalException(BCText.get().nonSignatureError); | |||
} | |||
} catch (PGPException e) { | |||
throw new JGitInternalException(BCText.get().signatureParseError, | |||
e); | |||
} | |||
Date signatureCreatedAt = signature.getCreationTime(); | |||
if (fingerprint == null && signer == null && keyId == null) { | |||
return new VerificationResult(signatureCreatedAt, null, null, null, | |||
false, false, TrustLevel.UNKNOWN, | |||
BCText.get().signatureNoKeyInfo); | |||
} | |||
if (fingerprint != null && keyId != null | |||
&& !fingerprint.endsWith(keyId)) { | |||
return new VerificationResult(signatureCreatedAt, signer, fingerprint, | |||
null, false, false, TrustLevel.UNKNOWN, | |||
MessageFormat.format(BCText.get().signatureInconsistent, | |||
keyId, fingerprint)); | |||
} | |||
if (fingerprint == null && keyId != null) { | |||
fingerprint = keyId; | |||
} | |||
// Try to find the public key | |||
String keySpec = '<' + signer + '>'; | |||
Object cached = null; | |||
PGPPublicKey publicKey = null; | |||
try { | |||
cached = byFingerprint.get(fingerprint); | |||
if (cached != null) { | |||
if (cached instanceof PGPPublicKey) { | |||
publicKey = (PGPPublicKey) cached; | |||
} | |||
} else if (!StringUtils.isEmptyOrNull(signer)) { | |||
cached = bySigner.get(signer); | |||
if (cached != null) { | |||
if (cached instanceof PGPPublicKey) { | |||
publicKey = (PGPPublicKey) cached; | |||
} | |||
} | |||
} | |||
if (cached == null) { | |||
publicKey = BouncyCastleGpgKeyLocator.findPublicKey(fingerprint, | |||
keySpec); | |||
} | |||
} catch (IOException | PGPException e) { | |||
throw new JGitInternalException( | |||
BCText.get().signatureKeyLookupError, e); | |||
} | |||
if (publicKey == null) { | |||
if (cached == null) { | |||
byFingerprint.put(fingerprint, NO_KEY); | |||
byFingerprint.put(keyId, NO_KEY); | |||
if (signer != null) { | |||
bySigner.put(signer, NO_KEY); | |||
} | |||
} | |||
return new VerificationResult(signatureCreatedAt, signer, | |||
fingerprint, null, false, false, TrustLevel.UNKNOWN, | |||
BCText.get().signatureNoPublicKey); | |||
} | |||
if (cached == null) { | |||
byFingerprint.put(fingerprint, publicKey); | |||
byFingerprint.put(keyId, publicKey); | |||
if (signer != null) { | |||
bySigner.put(signer, publicKey); | |||
} | |||
} | |||
String user = null; | |||
Iterator<String> userIds = publicKey.getUserIDs(); | |||
if (!StringUtils.isEmptyOrNull(signer)) { | |||
while (userIds.hasNext()) { | |||
String userId = userIds.next(); | |||
if (BouncyCastleGpgKeyLocator.containsSigningKey(userId, | |||
keySpec)) { | |||
user = userId; | |||
break; | |||
} | |||
} | |||
} | |||
if (user == null) { | |||
userIds = publicKey.getUserIDs(); | |||
if (userIds.hasNext()) { | |||
user = userIds.next(); | |||
} | |||
} | |||
boolean expired = false; | |||
long validFor = publicKey.getValidSeconds(); | |||
if (validFor > 0 && signatureCreatedAt != null) { | |||
Instant expiredAt = publicKey.getCreationTime().toInstant() | |||
.plusSeconds(validFor); | |||
expired = expiredAt.isBefore(signatureCreatedAt.toInstant()); | |||
} | |||
// Trust data is not defined in OpenPGP; the format is implementation | |||
// specific. We don't use the GPG trustdb but simply the trust packet | |||
// on the public key, if present. Even if present, it may or may not | |||
// be set. | |||
byte[] trustData = publicKey.getTrustData(); | |||
TrustLevel trust = parseGpgTrustPacket(trustData); | |||
boolean verified = false; | |||
try { | |||
signature.init( | |||
new JcaPGPContentVerifierBuilderProvider() | |||
.setProvider(BouncyCastleProvider.PROVIDER_NAME), | |||
publicKey); | |||
signature.update(data); | |||
verified = signature.verify(); | |||
} catch (PGPException e) { | |||
throw new JGitInternalException( | |||
BCText.get().signatureVerificationError, e); | |||
} | |||
return new VerificationResult(signatureCreatedAt, signer, fingerprint, user, | |||
verified, expired, trust, null); | |||
} | |||
private TrustLevel parseGpgTrustPacket(byte[] packet) { | |||
if (packet == null || packet.length < 6) { | |||
// A GPG trust packet has at least 6 bytes. | |||
return TrustLevel.UNKNOWN; | |||
} | |||
if (packet[2] != 'g' || packet[3] != 'p' || packet[4] != 'g') { | |||
// Not a GPG trust packet | |||
return TrustLevel.UNKNOWN; | |||
} | |||
int trust = packet[0] & 0x0F; | |||
switch (trust) { | |||
case 0: // No determined/set | |||
case 1: // Trust expired; i.e., calculation outdated or key expired | |||
case 2: // Undefined: not enough information to set | |||
return TrustLevel.UNKNOWN; | |||
case 3: | |||
return TrustLevel.NEVER; | |||
case 4: | |||
return TrustLevel.MARGINAL; | |||
case 5: | |||
return TrustLevel.FULL; | |||
case 6: | |||
return TrustLevel.ULTIMATE; | |||
default: | |||
return TrustLevel.UNKNOWN; | |||
} | |||
} | |||
@Override | |||
public void clear() { | |||
byFingerprint.clear(); | |||
bySigner.clear(); | |||
} | |||
private static class VerificationResult implements SignatureVerification { | |||
private final Date creationDate; | |||
private final String signer; | |||
private final String keyUser; | |||
private final String fingerprint; | |||
private final boolean verified; | |||
private final boolean expired; | |||
private final @NonNull TrustLevel trustLevel; | |||
private final String message; | |||
public VerificationResult(Date creationDate, String signer, | |||
String fingerprint, String user, boolean verified, | |||
boolean expired, @NonNull TrustLevel trust, String message) { | |||
this.creationDate = creationDate; | |||
this.signer = signer; | |||
this.fingerprint = fingerprint; | |||
this.keyUser = user; | |||
this.verified = verified; | |||
this.expired = expired; | |||
this.trustLevel = trust; | |||
this.message = message; | |||
} | |||
@Override | |||
public Date getCreationDate() { | |||
return creationDate; | |||
} | |||
@Override | |||
public String getSigner() { | |||
return signer; | |||
} | |||
@Override | |||
public String getKeyUser() { | |||
return keyUser; | |||
} | |||
@Override | |||
public String getKeyFingerprint() { | |||
return fingerprint; | |||
} | |||
@Override | |||
public boolean isExpired() { | |||
return expired; | |||
} | |||
@Override | |||
public TrustLevel getTrustLevel() { | |||
return trustLevel; | |||
} | |||
@Override | |||
public String getMessage() { | |||
return message; | |||
} | |||
@Override | |||
public boolean getVerified() { | |||
return verified; | |||
} | |||
} | |||
} |
@@ -0,0 +1,28 @@ | |||
/* | |||
* Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others | |||
* | |||
* This program and the accompanying materials are made available under the | |||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||
* https://www.eclipse.org/org/documents/edl-v10.php. | |||
* | |||
* SPDX-License-Identifier: BSD-3-Clause | |||
*/ | |||
package org.eclipse.jgit.gpg.bc.internal; | |||
import org.eclipse.jgit.lib.GpgSignatureVerifier; | |||
import org.eclipse.jgit.lib.GpgSignatureVerifierFactory; | |||
/** | |||
* A {@link GpgSignatureVerifierFactory} that creates | |||
* {@link GpgSignatureVerifier} instances that verify GPG signatures using | |||
* BouncyCastle and that do cache public keys. | |||
*/ | |||
public final class BouncyCastleGpgSignatureVerifierFactory | |||
extends GpgSignatureVerifierFactory { | |||
@Override | |||
public GpgSignatureVerifier getVerifier() { | |||
return new BouncyCastleGpgSignatureVerifier(); | |||
} | |||
} |
@@ -1,5 +1,5 @@ | |||
/* | |||
* Copyright (C) 2018, 2020, Salesforce and others | |||
* Copyright (C) 2018, 2021, Salesforce and others | |||
* | |||
* This program and the accompanying materials are made available under the | |||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||
@@ -34,18 +34,25 @@ import org.eclipse.jgit.annotations.NonNull; | |||
import org.eclipse.jgit.annotations.Nullable; | |||
import org.eclipse.jgit.api.errors.CanceledException; | |||
import org.eclipse.jgit.api.errors.JGitInternalException; | |||
import org.eclipse.jgit.api.errors.UnsupportedSigningFormatException; | |||
import org.eclipse.jgit.errors.UnsupportedCredentialItem; | |||
import org.eclipse.jgit.internal.JGitText; | |||
import org.eclipse.jgit.lib.CommitBuilder; | |||
import org.eclipse.jgit.lib.GpgConfig; | |||
import org.eclipse.jgit.lib.GpgObjectSigner; | |||
import org.eclipse.jgit.lib.GpgSignature; | |||
import org.eclipse.jgit.lib.GpgSigner; | |||
import org.eclipse.jgit.lib.ObjectBuilder; | |||
import org.eclipse.jgit.lib.PersonIdent; | |||
import org.eclipse.jgit.lib.GpgConfig.GpgFormat; | |||
import org.eclipse.jgit.transport.CredentialsProvider; | |||
import org.eclipse.jgit.util.StringUtils; | |||
/** | |||
* GPG Signer using BouncyCastle library | |||
* GPG Signer using the BouncyCastle library. | |||
*/ | |||
public class BouncyCastleGpgSigner extends GpgSigner { | |||
public class BouncyCastleGpgSigner extends GpgSigner | |||
implements GpgObjectSigner { | |||
private static void registerBouncyCastleProviderIfNecessary() { | |||
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { | |||
@@ -67,13 +74,32 @@ public class BouncyCastleGpgSigner extends GpgSigner { | |||
public boolean canLocateSigningKey(@Nullable String gpgSigningKey, | |||
PersonIdent committer, CredentialsProvider credentialsProvider) | |||
throws CanceledException { | |||
try { | |||
return canLocateSigningKey(gpgSigningKey, committer, | |||
credentialsProvider, null); | |||
} catch (UnsupportedSigningFormatException e) { | |||
// Cannot occur with a null config | |||
return false; | |||
} | |||
} | |||
@Override | |||
public boolean canLocateSigningKey(@Nullable String gpgSigningKey, | |||
PersonIdent committer, CredentialsProvider credentialsProvider, | |||
GpgConfig config) | |||
throws CanceledException, UnsupportedSigningFormatException { | |||
if (config != null && config.getKeyFormat() != GpgFormat.OPENPGP) { | |||
throw new UnsupportedSigningFormatException( | |||
JGitText.get().onlyOpenPgpSupportedForSigning); | |||
} | |||
try (BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt = new BouncyCastleGpgKeyPassphrasePrompt( | |||
credentialsProvider)) { | |||
BouncyCastleGpgKey gpgKey = locateSigningKey(gpgSigningKey, | |||
committer, passphrasePrompt); | |||
return gpgKey != null; | |||
} catch (PGPException | IOException | NoSuchAlgorithmException | |||
| NoSuchProviderException | URISyntaxException e) { | |||
} catch (CanceledException e) { | |||
throw e; | |||
} catch (Exception e) { | |||
return false; | |||
} | |||
} | |||
@@ -98,10 +124,28 @@ public class BouncyCastleGpgSigner extends GpgSigner { | |||
public void sign(@NonNull CommitBuilder commit, | |||
@Nullable String gpgSigningKey, @NonNull PersonIdent committer, | |||
CredentialsProvider credentialsProvider) throws CanceledException { | |||
try { | |||
signObject(commit, gpgSigningKey, committer, credentialsProvider, | |||
null); | |||
} catch (UnsupportedSigningFormatException e) { | |||
// Cannot occur with a null config | |||
} | |||
} | |||
@Override | |||
public void signObject(@NonNull ObjectBuilder object, | |||
@Nullable String gpgSigningKey, @NonNull PersonIdent committer, | |||
CredentialsProvider credentialsProvider, GpgConfig config) | |||
throws CanceledException, UnsupportedSigningFormatException { | |||
if (config != null && config.getKeyFormat() != GpgFormat.OPENPGP) { | |||
throw new UnsupportedSigningFormatException( | |||
JGitText.get().onlyOpenPgpSupportedForSigning); | |||
} | |||
try (BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt = new BouncyCastleGpgKeyPassphrasePrompt( | |||
credentialsProvider)) { | |||
BouncyCastleGpgKey gpgKey = locateSigningKey(gpgSigningKey, | |||
committer, passphrasePrompt); | |||
committer, | |||
passphrasePrompt); | |||
PGPSecretKey secretKey = gpgKey.getSecretKey(); | |||
if (secretKey == null) { | |||
throw new JGitInternalException( | |||
@@ -158,17 +202,17 @@ public class BouncyCastleGpgSigner extends GpgSigner { | |||
ByteArrayOutputStream buffer = new ByteArrayOutputStream(); | |||
try (BCPGOutputStream out = new BCPGOutputStream( | |||
new ArmoredOutputStream(buffer))) { | |||
signatureGenerator.update(commit.build()); | |||
signatureGenerator.update(object.build()); | |||
signatureGenerator.generate().encode(out); | |||
} | |||
commit.setGpgSignature(new GpgSignature(buffer.toByteArray())); | |||
object.setGpgSignature(new GpgSignature(buffer.toByteArray())); | |||
} catch (PGPException | IOException | NoSuchAlgorithmException | |||
| NoSuchProviderException | URISyntaxException e) { | |||
throw new JGitInternalException(e.getMessage(), e); | |||
} | |||
} | |||
private String extractSignerId(String pgpUserId) { | |||
static String extractSignerId(String pgpUserId) { | |||
int from = pgpUserId.indexOf('<'); | |||
if (from >= 0) { | |||
int to = pgpUserId.indexOf('>', from + 1); |
@@ -0,0 +1,322 @@ | |||
/* | |||
* Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others | |||
* | |||
* This program and the accompanying materials are made available under the | |||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||
* https://www.eclipse.org/org/documents/edl-v10.php. | |||
* | |||
* SPDX-License-Identifier: BSD-3-Clause | |||
*/ | |||
package org.eclipse.jgit.gpg.bc.internal.keys; | |||
import java.math.BigInteger; | |||
import java.nio.charset.StandardCharsets; | |||
import java.text.MessageFormat; | |||
import java.util.Arrays; | |||
import org.bouncycastle.asn1.ASN1ObjectIdentifier; | |||
import org.bouncycastle.asn1.cryptlib.CryptlibObjectIdentifiers; | |||
import org.bouncycastle.asn1.x9.ECNamedCurveTable; | |||
import org.bouncycastle.asn1.x9.X9ECParameters; | |||
import org.bouncycastle.bcpg.DSAPublicBCPGKey; | |||
import org.bouncycastle.bcpg.ECPublicBCPGKey; | |||
import org.bouncycastle.bcpg.ElGamalPublicBCPGKey; | |||
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; | |||
import org.bouncycastle.bcpg.RSAPublicBCPGKey; | |||
import org.bouncycastle.crypto.ec.CustomNamedCurves; | |||
import org.bouncycastle.math.ec.ECAlgorithms; | |||
import org.bouncycastle.math.field.FiniteField; | |||
import org.bouncycastle.openpgp.PGPException; | |||
import org.bouncycastle.openpgp.PGPPublicKey; | |||
import org.bouncycastle.util.encoders.Hex; | |||
import org.eclipse.jgit.annotations.NonNull; | |||
import org.eclipse.jgit.gpg.bc.internal.BCText; | |||
import org.eclipse.jgit.util.sha1.SHA1; | |||
/** | |||
* Utilities to compute the <em>keygrip</em> of a key. A keygrip is a SHA1 hash | |||
* over the public key parameters and is used internally by the gpg-agent to | |||
* find the secret key belonging to a public key: the secret key is stored in a | |||
* file under ~/.gnupg/private-keys-v1.d/ with a name "<keygrip>.key". While | |||
* this storage organization is an implementation detail of GPG, the way | |||
* keygrips are computed is not; they are computed by libgcrypt and their | |||
* definition is stable. | |||
*/ | |||
public final class KeyGrip { | |||
// Some OIDs apparently unknown to BouncyCastle. | |||
private static String OID_OPENPGP_ED25519 = "1.3.6.1.4.1.11591.15.1"; //$NON-NLS-1$ | |||
private static String OID_RFC8410_CURVE25519 = "1.3.101.110"; //$NON-NLS-1$ | |||
private static String OID_RFC8410_ED25519 = "1.3.101.112"; //$NON-NLS-1$ | |||
private KeyGrip() { | |||
// No instantiation | |||
} | |||
/** | |||
* Computes the keygrip for a {@link PGPPublicKey}. | |||
* | |||
* @param publicKey | |||
* to get the keygrip of | |||
* @return the keygrip | |||
* @throws PGPException | |||
* if an unknown key type is encountered. | |||
*/ | |||
@NonNull | |||
public static byte[] getKeyGrip(PGPPublicKey publicKey) | |||
throws PGPException { | |||
SHA1 grip = SHA1.newInstance(); | |||
grip.setDetectCollision(false); | |||
switch (publicKey.getAlgorithm()) { | |||
case PublicKeyAlgorithmTags.RSA_GENERAL: | |||
case PublicKeyAlgorithmTags.RSA_ENCRYPT: | |||
case PublicKeyAlgorithmTags.RSA_SIGN: | |||
BigInteger modulus = ((RSAPublicBCPGKey) publicKey | |||
.getPublicKeyPacket().getKey()).getModulus(); | |||
hash(grip, modulus.toByteArray()); | |||
break; | |||
case PublicKeyAlgorithmTags.DSA: | |||
DSAPublicBCPGKey dsa = (DSAPublicBCPGKey) publicKey | |||
.getPublicKeyPacket().getKey(); | |||
hash(grip, dsa.getP().toByteArray(), 'p', true); | |||
hash(grip, dsa.getQ().toByteArray(), 'q', true); | |||
hash(grip, dsa.getG().toByteArray(), 'g', true); | |||
hash(grip, dsa.getY().toByteArray(), 'y', true); | |||
break; | |||
case PublicKeyAlgorithmTags.ELGAMAL_GENERAL: | |||
case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: | |||
ElGamalPublicBCPGKey eg = (ElGamalPublicBCPGKey) publicKey | |||
.getPublicKeyPacket().getKey(); | |||
hash(grip, eg.getP().toByteArray(), 'p', true); | |||
hash(grip, eg.getG().toByteArray(), 'g', true); | |||
hash(grip, eg.getY().toByteArray(), 'y', true); | |||
break; | |||
case PublicKeyAlgorithmTags.ECDH: | |||
case PublicKeyAlgorithmTags.ECDSA: | |||
case PublicKeyAlgorithmTags.EDDSA: | |||
ECPublicBCPGKey ec = (ECPublicBCPGKey) publicKey | |||
.getPublicKeyPacket().getKey(); | |||
ASN1ObjectIdentifier curveOID = ec.getCurveOID(); | |||
// BC doesn't know these OIDs. | |||
if (OID_OPENPGP_ED25519.equals(curveOID.getId()) | |||
|| OID_RFC8410_ED25519.equals(curveOID.getId())) { | |||
return hashEd25519(grip, ec.getEncodedPoint()); | |||
} else if (CryptlibObjectIdentifiers.curvey25519.equals(curveOID) | |||
|| OID_RFC8410_CURVE25519.equals(curveOID.getId())) { | |||
// curvey25519 actually is the OpenPGP OID for Curve25519 and is | |||
// known to BC, but the parameters are for the short Weierstrass | |||
// form. See https://github.com/bcgit/bc-java/issues/399 . | |||
// libgcrypt uses Montgomery form. | |||
return hashCurve25519(grip, ec.getEncodedPoint()); | |||
} | |||
X9ECParameters params = getX9Parameters(curveOID); | |||
if (params == null) { | |||
throw new PGPException(MessageFormat | |||
.format(BCText.get().unknownCurve, curveOID.getId())); | |||
} | |||
// Need to write p, a, b, g, n, q | |||
BigInteger q = ec.getEncodedPoint(); | |||
byte[] g = params.getG().getEncoded(false); | |||
BigInteger a = params.getCurve().getA().toBigInteger(); | |||
BigInteger b = params.getCurve().getB().toBigInteger(); | |||
BigInteger n = params.getN(); | |||
BigInteger p = null; | |||
FiniteField field = params.getCurve().getField(); | |||
if (ECAlgorithms.isFpField(field)) { | |||
p = field.getCharacteristic(); | |||
} | |||
if (p == null) { | |||
// Don't know... | |||
throw new PGPException(MessageFormat.format( | |||
BCText.get().unknownCurveParameters, curveOID.getId())); | |||
} | |||
hash(grip, p.toByteArray(), 'p', false); | |||
hash(grip, a.toByteArray(), 'a', false); | |||
hash(grip, b.toByteArray(), 'b', false); | |||
hash(grip, g, 'g', false); | |||
hash(grip, n.toByteArray(), 'n', false); | |||
if (publicKey.getAlgorithm() == PublicKeyAlgorithmTags.EDDSA) { | |||
hashQ25519(grip, q); | |||
} else { | |||
hash(grip, q.toByteArray(), 'q', false); | |||
} | |||
break; | |||
default: | |||
throw new PGPException( | |||
MessageFormat.format(BCText.get().unknownKeyType, | |||
Integer.toString(publicKey.getAlgorithm()))); | |||
} | |||
return grip.digest(); | |||
} | |||
private static void hash(SHA1 grip, byte[] data) { | |||
// Need to skip leading zero bytes | |||
int i = 0; | |||
while (i < data.length && data[i] == 0) { | |||
i++; | |||
} | |||
int length = data.length - i; | |||
if (i < data.length) { | |||
if ((data[i] & 0x80) != 0) { | |||
grip.update((byte) 0); | |||
} | |||
grip.update(data, i, length); | |||
} | |||
} | |||
private static void hash(SHA1 grip, byte[] data, char id, boolean zeroPad) { | |||
// Need to skip leading zero bytes | |||
int i = 0; | |||
while (i < data.length && data[i] == 0) { | |||
i++; | |||
} | |||
int length = data.length - i; | |||
boolean addZero = false; | |||
if (i < data.length && zeroPad && (data[i] & 0x80) != 0) { | |||
addZero = true; | |||
} | |||
// libgcrypt includes an SExp in the hash | |||
String prefix = "(1:" + id + (addZero ? length + 1 : length) + ':'; //$NON-NLS-1$ | |||
grip.update(prefix.getBytes(StandardCharsets.US_ASCII)); | |||
// For some items, gcrypt prepends a zero byte if the high bit is set | |||
if (addZero) { | |||
grip.update((byte) 0); | |||
} | |||
if (i < data.length) { | |||
grip.update(data, i, length); | |||
} | |||
grip.update((byte) ')'); | |||
} | |||
private static void hashQ25519(SHA1 grip, BigInteger q) | |||
throws PGPException { | |||
byte[] data = q.toByteArray(); | |||
switch (data[0]) { | |||
case 0x04: | |||
if (data.length != 65) { | |||
throw new PGPException(MessageFormat.format( | |||
BCText.get().corrupt25519Key, Hex.toHexString(data))); | |||
} | |||
// Uncompressed: should not occur with ed25519 or curve25519 | |||
throw new PGPException(MessageFormat.format( | |||
BCText.get().uncompressed25519Key, Hex.toHexString(data))); | |||
case 0x40: | |||
if (data.length != 33) { | |||
throw new PGPException(MessageFormat.format( | |||
BCText.get().corrupt25519Key, Hex.toHexString(data))); | |||
} | |||
// Compressed; normal case. Skip prefix. | |||
hash(grip, Arrays.copyOfRange(data, 1, data.length), 'q', false); | |||
break; | |||
default: | |||
if (data.length != 32) { | |||
throw new PGPException(MessageFormat.format( | |||
BCText.get().corrupt25519Key, Hex.toHexString(data))); | |||
} | |||
// Compressed format without prefix. Should not occur? | |||
hash(grip, data, 'q', false); | |||
break; | |||
} | |||
} | |||
/** | |||
* Computes the keygrip for an ed25519 public key. | |||
* <p> | |||
* Package-visible for tests only. | |||
* </p> | |||
* | |||
* @param grip | |||
* initialized {@link SHA1} | |||
* @param q | |||
* the public key's EC point | |||
* @return the keygrip | |||
* @throws PGPException | |||
* if q indicates uncompressed format | |||
*/ | |||
@SuppressWarnings("nls") | |||
static byte[] hashEd25519(SHA1 grip, BigInteger q) throws PGPException { | |||
// For the values, see RFC 7748: https://tools.ietf.org/html/rfc7748 | |||
// p = 2^255 - 19 | |||
hash(grip, Hex.decodeStrict( | |||
"7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED"), | |||
'p', false); | |||
// Field: a = 1 | |||
hash(grip, new byte[] { 0x01 }, 'a', false); | |||
// Field: b = 121665/121666 (mod p) | |||
// See Berstein et.al., "Twisted Edwards Curves", | |||
// https://doi.org/10.1007/978-3-540-68164-9_26 | |||
hash(grip, Hex.decodeStrict( | |||
"2DFC9311D490018C7338BF8688861767FF8FF5B2BEBE27548A14B235ECA6874A"), | |||
'b', false); | |||
// Generator point with affine X,Y | |||
// @formatter:off | |||
// X(P) = 15112221349535400772501151409588531511454012693041857206046113283949847762202 | |||
// Y(P) = 46316835694926478169428394003475163141307993866256225615783033603165251855960 | |||
// the "04" signifies uncompressed format. | |||
// @formatter:on | |||
hash(grip, Hex.decodeStrict("04" | |||
+ "216936D3CD6E53FEC0A4E231FDD6DC5C692CC7609525A7B2C9562D608F25D51A" | |||
+ "6666666666666666666666666666666666666666666666666666666666666658"), | |||
'g', false); | |||
// order = 2^252 + 0x14def9dea2f79cd65812631a5cf5d3ed | |||
hash(grip, Hex.decodeStrict( | |||
"1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED"), | |||
'n', false); | |||
hashQ25519(grip, q); | |||
return grip.digest(); | |||
} | |||
/** | |||
* Computes the keygrip for a curve25519 public key. | |||
* <p> | |||
* Package-visible for tests only. | |||
* </p> | |||
* | |||
* @param grip | |||
* initialized {@link SHA1} | |||
* @param q | |||
* the public key's EC point | |||
* @return the keygrip | |||
* @throws PGPException | |||
* if q indicates uncompressed format | |||
*/ | |||
@SuppressWarnings("nls") | |||
static byte[] hashCurve25519(SHA1 grip, BigInteger q) throws PGPException { | |||
hash(grip, Hex.decodeStrict( | |||
"7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED"), | |||
'p', false); | |||
// Unclear: RFC 7748 says A = 486662. This value here is (A-2)/4 = | |||
// 121665. Compare ecc-curves.c in libgcrypt: | |||
// https://github.com/gpg/libgcrypt/blob/361a058/cipher/ecc-curves.c#L146 | |||
hash(grip, new byte[] { 0x01, (byte) 0xDB, 0x41 }, 'a', false); | |||
hash(grip, new byte[] { 0x01 }, 'b', false); | |||
// libgcrypt uses the old g.y value before the erratum to RFC 7748 for | |||
// the keygrip. The new value would be | |||
// 5F51E65E475F794B1FE122D388B72EB36DC2B28192839E4DD6163A5D81312C14. See | |||
// https://www.rfc-editor.org/errata/eid4730 and | |||
// https://github.com/gpg/libgcrypt/commit/f67b6492e0b0 | |||
hash(grip, Hex.decodeStrict("04" | |||
+ "0000000000000000000000000000000000000000000000000000000000000009" | |||
+ "20AE19A1B8A086B4E01EDD2C7748D14C923D4D7E6D7C61B229E9C5A27ECED3D9"), | |||
'g', false); | |||
hash(grip, Hex.decodeStrict( | |||
"1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED"), | |||
'n', false); | |||
hashQ25519(grip, q); | |||
return grip.digest(); | |||
} | |||
private static X9ECParameters getX9Parameters( | |||
ASN1ObjectIdentifier curveOID) { | |||
X9ECParameters params = CustomNamedCurves.getByOID(curveOID); | |||
if (params == null) { | |||
params = ECNamedCurveTable.getByOID(curveOID); | |||
} | |||
return params; | |||
} | |||
} |
@@ -0,0 +1,121 @@ | |||
/* | |||
* Copyright (C) 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others | |||
* | |||
* This program and the accompanying materials are made available under the | |||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||
* https://www.eclipse.org/org/documents/edl-v10.php. | |||
* | |||
* SPDX-License-Identifier: BSD-3-Clause | |||
*/ | |||
package org.eclipse.jgit.gpg.bc.internal.keys; | |||
import java.security.NoSuchAlgorithmException; | |||
import java.text.MessageFormat; | |||
import javax.crypto.Cipher; | |||
import javax.crypto.SecretKey; | |||
import javax.crypto.spec.IvParameterSpec; | |||
import javax.crypto.spec.SecretKeySpec; | |||
import org.bouncycastle.openpgp.PGPException; | |||
import org.bouncycastle.openpgp.PGPUtil; | |||
import org.bouncycastle.openpgp.operator.PBEProtectionRemoverFactory; | |||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; | |||
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; | |||
import org.bouncycastle.util.Arrays; | |||
import org.eclipse.jgit.gpg.bc.internal.BCText; | |||
/** | |||
* A {@link PBEProtectionRemoverFactory} using AES/OCB/NoPadding for decryption. | |||
* It accepts an AAD in the factory's constructor, so the factory can be used to | |||
* create a {@link PBESecretKeyDecryptor} only for a particular input. | |||
* <p> | |||
* For JGit's needs, this is sufficient, but for a general upstream | |||
* implementation that limitation might not be acceptable. | |||
* </p> | |||
*/ | |||
class OCBPBEProtectionRemoverFactory | |||
implements PBEProtectionRemoverFactory { | |||
private final PGPDigestCalculatorProvider calculatorProvider; | |||
private final char[] passphrase; | |||
private final byte[] aad; | |||
/** | |||
* Creates a new factory instance with the given parameters. | |||
* <p> | |||
* Because the AAD is given at factory level, the {@link PBESecretKeyDecryptor}s | |||
* created by the factory can be used to decrypt only a particular input | |||
* matching this AAD. | |||
* </p> | |||
* | |||
* @param passphrase to use for secret key derivation | |||
* @param calculatorProvider for computing digests | |||
* @param aad for the OCB decryption | |||
*/ | |||
OCBPBEProtectionRemoverFactory(char[] passphrase, | |||
PGPDigestCalculatorProvider calculatorProvider, byte[] aad) { | |||
this.calculatorProvider = calculatorProvider; | |||
this.passphrase = passphrase; | |||
this.aad = aad; | |||
} | |||
@Override | |||
public PBESecretKeyDecryptor createDecryptor(String protection) | |||
throws PGPException { | |||
return new PBESecretKeyDecryptor(passphrase, calculatorProvider) { | |||
@Override | |||
public byte[] recoverKeyData(int encAlgorithm, byte[] key, | |||
byte[] iv, byte[] encrypted, int encryptedOffset, | |||
int encryptedLength) throws PGPException { | |||
String algorithmName = PGPUtil | |||
.getSymmetricCipherName(encAlgorithm); | |||
byte[] decrypted = null; | |||
try { | |||
Cipher c = Cipher | |||
.getInstance(algorithmName + "/OCB/NoPadding"); //$NON-NLS-1$ | |||
SecretKey secretKey = new SecretKeySpec(key, algorithmName); | |||
c.init(Cipher.DECRYPT_MODE, secretKey, | |||
new IvParameterSpec(iv)); | |||
c.updateAAD(aad); | |||
decrypted = new byte[c.getOutputSize(encryptedLength)]; | |||
int decryptedLength = c.update(encrypted, encryptedOffset, | |||
encryptedLength, decrypted); | |||
// doFinal() for OCB will check the MAC and throw an | |||
// exception if it doesn't match | |||
decryptedLength += c.doFinal(decrypted, decryptedLength); | |||
if (decryptedLength != decrypted.length) { | |||
throw new PGPException(MessageFormat.format( | |||
BCText.get().cryptWrongDecryptedLength, | |||
Integer.valueOf(decryptedLength), | |||
Integer.valueOf(decrypted.length))); | |||
} | |||
byte[] result = decrypted; | |||
decrypted = null; // Don't clear in finally | |||
return result; | |||
} catch (NoClassDefFoundError e) { | |||
String msg = MessageFormat.format( | |||
BCText.get().gpgNoSuchAlgorithm, | |||
algorithmName + "/OCB"); //$NON-NLS-1$ | |||
throw new PGPException(msg, | |||
new NoSuchAlgorithmException(msg, e)); | |||
} catch (PGPException e) { | |||
throw e; | |||
} catch (Exception e) { | |||
throw new PGPException( | |||
MessageFormat.format(BCText.get().cryptCipherError, | |||
e.getLocalizedMessage()), | |||
e); | |||
} finally { | |||
if (decrypted != null) { | |||
// Prevent halfway decrypted data leaking. | |||
Arrays.fill(decrypted, (byte) 0); | |||
} | |||
} | |||
} | |||
}; | |||
} | |||
} |
@@ -0,0 +1,826 @@ | |||
/* | |||
* Copyright (c) 2000-2021 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org) | |||
* <p> | |||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software | |||
* and associated documentation files (the "Software"), to deal in the Software without restriction, | |||
*including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, | |||
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, | |||
* subject to the following conditions: | |||
* </p> | |||
* <p> | |||
* The above copyright notice and this permission notice shall be included in all copies or substantial | |||
* portions of the Software. | |||
* </p> | |||
* <p> | |||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, | |||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR | |||
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | |||
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |||
* DEALINGS IN THE SOFTWARE. | |||
* </p> | |||
*/ | |||
package org.eclipse.jgit.gpg.bc.internal.keys; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.OutputStream; | |||
import java.math.BigInteger; | |||
import java.util.Date; | |||
import org.bouncycastle.asn1.x9.ECNamedCurveTable; | |||
import org.bouncycastle.bcpg.DSAPublicBCPGKey; | |||
import org.bouncycastle.bcpg.DSASecretBCPGKey; | |||
import org.bouncycastle.bcpg.ECDSAPublicBCPGKey; | |||
import org.bouncycastle.bcpg.ECPublicBCPGKey; | |||
import org.bouncycastle.bcpg.ECSecretBCPGKey; | |||
import org.bouncycastle.bcpg.ElGamalPublicBCPGKey; | |||
import org.bouncycastle.bcpg.ElGamalSecretBCPGKey; | |||
import org.bouncycastle.bcpg.HashAlgorithmTags; | |||
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; | |||
import org.bouncycastle.bcpg.PublicKeyPacket; | |||
import org.bouncycastle.bcpg.RSAPublicBCPGKey; | |||
import org.bouncycastle.bcpg.RSASecretBCPGKey; | |||
import org.bouncycastle.bcpg.S2K; | |||
import org.bouncycastle.bcpg.SecretKeyPacket; | |||
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; | |||
import org.bouncycastle.openpgp.PGPException; | |||
import org.bouncycastle.openpgp.PGPPublicKey; | |||
import org.bouncycastle.openpgp.PGPSecretKey; | |||
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; | |||
import org.bouncycastle.openpgp.operator.PBEProtectionRemoverFactory; | |||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; | |||
import org.bouncycastle.openpgp.operator.PGPDigestCalculator; | |||
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; | |||
import org.bouncycastle.util.Arrays; | |||
import org.bouncycastle.util.Strings; | |||
/** | |||
* A parser for secret keys stored in s-expressions. Original BouncyCastle code | |||
* modified by the JGit team to: | |||
* <ul> | |||
* <li>handle unencrypted DSA, EC, and ElGamal keys (upstream only handles | |||
* unencrypted RSA), and</li> | |||
* <li>handle secret keys using AES/OCB as encryption (those don't have a | |||
* hash).</li> | |||
* </ul> | |||
*/ | |||
@SuppressWarnings("nls") | |||
public class SExprParser { | |||
private final PGPDigestCalculatorProvider digestProvider; | |||
/** | |||
* Base constructor. | |||
* | |||
* @param digestProvider | |||
* a provider for digest calculations. Used to confirm key | |||
* protection hashes. | |||
*/ | |||
public SExprParser(PGPDigestCalculatorProvider digestProvider) { | |||
this.digestProvider = digestProvider; | |||
} | |||
/** | |||
* Parse a secret key from one of the GPG S expression keys associating it | |||
* with the passed in public key. | |||
* | |||
* @param inputStream | |||
* to read from | |||
* @param keyProtectionRemoverFactory | |||
* for decrypting encrypted keys | |||
* @param pubKey | |||
* the private key should belong to | |||
* | |||
* @return a secret key object. | |||
* @throws IOException | |||
* @throws PGPException | |||
*/ | |||
public PGPSecretKey parseSecretKey(InputStream inputStream, | |||
PBEProtectionRemoverFactory keyProtectionRemoverFactory, | |||
PGPPublicKey pubKey) throws IOException, PGPException { | |||
SXprUtils.skipOpenParenthesis(inputStream); | |||
String type; | |||
type = SXprUtils.readString(inputStream, inputStream.read()); | |||
if (type.equals("protected-private-key") | |||
|| type.equals("private-key")) { | |||
SXprUtils.skipOpenParenthesis(inputStream); | |||
String keyType = SXprUtils.readString(inputStream, | |||
inputStream.read()); | |||
if (keyType.equals("ecc")) { | |||
SXprUtils.skipOpenParenthesis(inputStream); | |||
String curveID = SXprUtils.readString(inputStream, | |||
inputStream.read()); | |||
String curveName = SXprUtils.readString(inputStream, | |||
inputStream.read()); | |||
SXprUtils.skipCloseParenthesis(inputStream); | |||
byte[] qVal; | |||
SXprUtils.skipOpenParenthesis(inputStream); | |||
type = SXprUtils.readString(inputStream, inputStream.read()); | |||
if (type.equals("q")) { | |||
qVal = SXprUtils.readBytes(inputStream, inputStream.read()); | |||
} else { | |||
throw new PGPException("no q value found"); | |||
} | |||
SXprUtils.skipCloseParenthesis(inputStream); | |||
BigInteger d = processECSecretKey(inputStream, curveID, | |||
curveName, qVal, keyProtectionRemoverFactory); | |||
if (curveName.startsWith("NIST ")) { | |||
curveName = curveName.substring("NIST ".length()); | |||
} | |||
ECPublicBCPGKey basePubKey = new ECDSAPublicBCPGKey( | |||
ECNamedCurveTable.getOID(curveName), | |||
new BigInteger(1, qVal)); | |||
ECPublicBCPGKey assocPubKey = (ECPublicBCPGKey) pubKey | |||
.getPublicKeyPacket().getKey(); | |||
if (!basePubKey.getCurveOID().equals(assocPubKey.getCurveOID()) | |||
|| !basePubKey.getEncodedPoint() | |||
.equals(assocPubKey.getEncodedPoint())) { | |||
throw new PGPException( | |||
"passed in public key does not match secret key"); | |||
} | |||
return new PGPSecretKey( | |||
new SecretKeyPacket(pubKey.getPublicKeyPacket(), | |||
SymmetricKeyAlgorithmTags.NULL, null, null, | |||
new ECSecretBCPGKey(d).getEncoded()), | |||
pubKey); | |||
} else if (keyType.equals("dsa")) { | |||
BigInteger p = readBigInteger("p", inputStream); | |||
BigInteger q = readBigInteger("q", inputStream); | |||
BigInteger g = readBigInteger("g", inputStream); | |||
BigInteger y = readBigInteger("y", inputStream); | |||
BigInteger x = processDSASecretKey(inputStream, p, q, g, y, | |||
keyProtectionRemoverFactory); | |||
DSAPublicBCPGKey basePubKey = new DSAPublicBCPGKey(p, q, g, y); | |||
DSAPublicBCPGKey assocPubKey = (DSAPublicBCPGKey) pubKey | |||
.getPublicKeyPacket().getKey(); | |||
if (!basePubKey.getP().equals(assocPubKey.getP()) | |||
|| !basePubKey.getQ().equals(assocPubKey.getQ()) | |||
|| !basePubKey.getG().equals(assocPubKey.getG()) | |||
|| !basePubKey.getY().equals(assocPubKey.getY())) { | |||
throw new PGPException( | |||
"passed in public key does not match secret key"); | |||
} | |||
return new PGPSecretKey( | |||
new SecretKeyPacket(pubKey.getPublicKeyPacket(), | |||
SymmetricKeyAlgorithmTags.NULL, null, null, | |||
new DSASecretBCPGKey(x).getEncoded()), | |||
pubKey); | |||
} else if (keyType.equals("elg")) { | |||
BigInteger p = readBigInteger("p", inputStream); | |||
BigInteger g = readBigInteger("g", inputStream); | |||
BigInteger y = readBigInteger("y", inputStream); | |||
BigInteger x = processElGamalSecretKey(inputStream, p, g, y, | |||
keyProtectionRemoverFactory); | |||
ElGamalPublicBCPGKey basePubKey = new ElGamalPublicBCPGKey(p, g, | |||
y); | |||
ElGamalPublicBCPGKey assocPubKey = (ElGamalPublicBCPGKey) pubKey | |||
.getPublicKeyPacket().getKey(); | |||
if (!basePubKey.getP().equals(assocPubKey.getP()) | |||
|| !basePubKey.getG().equals(assocPubKey.getG()) | |||
|| !basePubKey.getY().equals(assocPubKey.getY())) { | |||
throw new PGPException( | |||
"passed in public key does not match secret key"); | |||
} | |||
return new PGPSecretKey( | |||
new SecretKeyPacket(pubKey.getPublicKeyPacket(), | |||
SymmetricKeyAlgorithmTags.NULL, null, null, | |||
new ElGamalSecretBCPGKey(x).getEncoded()), | |||
pubKey); | |||
} else if (keyType.equals("rsa")) { | |||
BigInteger n = readBigInteger("n", inputStream); | |||
BigInteger e = readBigInteger("e", inputStream); | |||
BigInteger[] values = processRSASecretKey(inputStream, n, e, | |||
keyProtectionRemoverFactory); | |||
// TODO: type of RSA key? | |||
RSAPublicBCPGKey basePubKey = new RSAPublicBCPGKey(n, e); | |||
RSAPublicBCPGKey assocPubKey = (RSAPublicBCPGKey) pubKey | |||
.getPublicKeyPacket().getKey(); | |||
if (!basePubKey.getModulus().equals(assocPubKey.getModulus()) | |||
|| !basePubKey.getPublicExponent() | |||
.equals(assocPubKey.getPublicExponent())) { | |||
throw new PGPException( | |||
"passed in public key does not match secret key"); | |||
} | |||
return new PGPSecretKey(new SecretKeyPacket( | |||
pubKey.getPublicKeyPacket(), | |||
SymmetricKeyAlgorithmTags.NULL, null, null, | |||
new RSASecretBCPGKey(values[0], values[1], values[2]) | |||
.getEncoded()), | |||
pubKey); | |||
} else { | |||
throw new PGPException("unknown key type: " + keyType); | |||
} | |||
} | |||
throw new PGPException("unknown key type found"); | |||
} | |||
/** | |||
* Parse a secret key from one of the GPG S expression keys. | |||
* | |||
* @param inputStream | |||
* to read from | |||
* @param keyProtectionRemoverFactory | |||
* for decrypting encrypted keys | |||
* @param fingerPrintCalculator | |||
* for calculating key fingerprints | |||
* | |||
* @return a secret key object. | |||
* @throws IOException | |||
* @throws PGPException | |||
*/ | |||
public PGPSecretKey parseSecretKey(InputStream inputStream, | |||
PBEProtectionRemoverFactory keyProtectionRemoverFactory, | |||
KeyFingerPrintCalculator fingerPrintCalculator) | |||
throws IOException, PGPException { | |||
SXprUtils.skipOpenParenthesis(inputStream); | |||
String type; | |||
type = SXprUtils.readString(inputStream, inputStream.read()); | |||
if (type.equals("protected-private-key") | |||
|| type.equals("private-key")) { | |||
SXprUtils.skipOpenParenthesis(inputStream); | |||
String keyType = SXprUtils.readString(inputStream, | |||
inputStream.read()); | |||
if (keyType.equals("ecc")) { | |||
SXprUtils.skipOpenParenthesis(inputStream); | |||
String curveID = SXprUtils.readString(inputStream, | |||
inputStream.read()); | |||
String curveName = SXprUtils.readString(inputStream, | |||
inputStream.read()); | |||
if (curveName.startsWith("NIST ")) { | |||
curveName = curveName.substring("NIST ".length()); | |||
} | |||
SXprUtils.skipCloseParenthesis(inputStream); | |||
byte[] qVal; | |||
SXprUtils.skipOpenParenthesis(inputStream); | |||
type = SXprUtils.readString(inputStream, inputStream.read()); | |||
if (type.equals("q")) { | |||
qVal = SXprUtils.readBytes(inputStream, inputStream.read()); | |||
} else { | |||
throw new PGPException("no q value found"); | |||
} | |||
PublicKeyPacket pubPacket = new PublicKeyPacket( | |||
PublicKeyAlgorithmTags.ECDSA, new Date(), | |||
new ECDSAPublicBCPGKey( | |||
ECNamedCurveTable.getOID(curveName), | |||
new BigInteger(1, qVal))); | |||
SXprUtils.skipCloseParenthesis(inputStream); | |||
BigInteger d = processECSecretKey(inputStream, curveID, | |||
curveName, qVal, keyProtectionRemoverFactory); | |||
return new PGPSecretKey( | |||
new SecretKeyPacket(pubPacket, | |||
SymmetricKeyAlgorithmTags.NULL, null, null, | |||
new ECSecretBCPGKey(d).getEncoded()), | |||
new PGPPublicKey(pubPacket, fingerPrintCalculator)); | |||
} else if (keyType.equals("dsa")) { | |||
BigInteger p = readBigInteger("p", inputStream); | |||
BigInteger q = readBigInteger("q", inputStream); | |||
BigInteger g = readBigInteger("g", inputStream); | |||
BigInteger y = readBigInteger("y", inputStream); | |||
BigInteger x = processDSASecretKey(inputStream, p, q, g, y, | |||
keyProtectionRemoverFactory); | |||
PublicKeyPacket pubPacket = new PublicKeyPacket( | |||
PublicKeyAlgorithmTags.DSA, new Date(), | |||
new DSAPublicBCPGKey(p, q, g, y)); | |||
return new PGPSecretKey( | |||
new SecretKeyPacket(pubPacket, | |||
SymmetricKeyAlgorithmTags.NULL, null, null, | |||
new DSASecretBCPGKey(x).getEncoded()), | |||
new PGPPublicKey(pubPacket, fingerPrintCalculator)); | |||
} else if (keyType.equals("elg")) { | |||
BigInteger p = readBigInteger("p", inputStream); | |||
BigInteger g = readBigInteger("g", inputStream); | |||
BigInteger y = readBigInteger("y", inputStream); | |||
BigInteger x = processElGamalSecretKey(inputStream, p, g, y, | |||
keyProtectionRemoverFactory); | |||
PublicKeyPacket pubPacket = new PublicKeyPacket( | |||
PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT, new Date(), | |||
new ElGamalPublicBCPGKey(p, g, y)); | |||
return new PGPSecretKey( | |||
new SecretKeyPacket(pubPacket, | |||
SymmetricKeyAlgorithmTags.NULL, null, null, | |||
new ElGamalSecretBCPGKey(x).getEncoded()), | |||
new PGPPublicKey(pubPacket, fingerPrintCalculator)); | |||
} else if (keyType.equals("rsa")) { | |||
BigInteger n = readBigInteger("n", inputStream); | |||
BigInteger e = readBigInteger("e", inputStream); | |||
BigInteger[] values = processRSASecretKey(inputStream, n, e, | |||
keyProtectionRemoverFactory); | |||
// TODO: type of RSA key? | |||
PublicKeyPacket pubPacket = new PublicKeyPacket( | |||
PublicKeyAlgorithmTags.RSA_GENERAL, new Date(), | |||
new RSAPublicBCPGKey(n, e)); | |||
return new PGPSecretKey( | |||
new SecretKeyPacket(pubPacket, | |||
SymmetricKeyAlgorithmTags.NULL, null, null, | |||
new RSASecretBCPGKey(values[0], values[1], | |||
values[2]).getEncoded()), | |||
new PGPPublicKey(pubPacket, fingerPrintCalculator)); | |||
} else { | |||
throw new PGPException("unknown key type: " + keyType); | |||
} | |||
} | |||
throw new PGPException("unknown key type found"); | |||
} | |||
private BigInteger readBigInteger(String expectedType, | |||
InputStream inputStream) throws IOException, PGPException { | |||
SXprUtils.skipOpenParenthesis(inputStream); | |||
String type = SXprUtils.readString(inputStream, inputStream.read()); | |||
if (!type.equals(expectedType)) { | |||
throw new PGPException(expectedType + " value expected"); | |||
} | |||
byte[] nBytes = SXprUtils.readBytes(inputStream, inputStream.read()); | |||
BigInteger v = new BigInteger(1, nBytes); | |||
SXprUtils.skipCloseParenthesis(inputStream); | |||
return v; | |||
} | |||
private static byte[][] extractData(InputStream inputStream, | |||
PBEProtectionRemoverFactory keyProtectionRemoverFactory) | |||
throws PGPException, IOException { | |||
byte[] data; | |||
byte[] protectedAt = null; | |||
SXprUtils.skipOpenParenthesis(inputStream); | |||
String type = SXprUtils.readString(inputStream, inputStream.read()); | |||
if (type.equals("protected")) { | |||
String protection = SXprUtils.readString(inputStream, | |||
inputStream.read()); | |||
SXprUtils.skipOpenParenthesis(inputStream); | |||
S2K s2k = SXprUtils.parseS2K(inputStream); | |||
byte[] iv = SXprUtils.readBytes(inputStream, inputStream.read()); | |||
SXprUtils.skipCloseParenthesis(inputStream); | |||
byte[] secKeyData = SXprUtils.readBytes(inputStream, | |||
inputStream.read()); | |||
SXprUtils.skipCloseParenthesis(inputStream); | |||
PBESecretKeyDecryptor keyDecryptor = keyProtectionRemoverFactory | |||
.createDecryptor(protection); | |||
// TODO: recognise other algorithms | |||
byte[] key = keyDecryptor.makeKeyFromPassPhrase( | |||
SymmetricKeyAlgorithmTags.AES_128, s2k); | |||
data = keyDecryptor.recoverKeyData( | |||
SymmetricKeyAlgorithmTags.AES_128, key, iv, secKeyData, 0, | |||
secKeyData.length); | |||
// check if protected at is present | |||
if (inputStream.read() == '(') { | |||
ByteArrayOutputStream bOut = new ByteArrayOutputStream(); | |||
bOut.write('('); | |||
int ch; | |||
while ((ch = inputStream.read()) >= 0 && ch != ')') { | |||
bOut.write(ch); | |||
} | |||
if (ch != ')') { | |||
throw new IOException("unexpected end to SExpr"); | |||
} | |||
bOut.write(')'); | |||
protectedAt = bOut.toByteArray(); | |||
} | |||
SXprUtils.skipCloseParenthesis(inputStream); | |||
SXprUtils.skipCloseParenthesis(inputStream); | |||
} else if (type.equals("d") || type.equals("x")) { | |||
// JGit modification: unencrypted DSA or ECC keys can have an "x" | |||
// here | |||
return null; | |||
} else { | |||
throw new PGPException("protected block not found"); | |||
} | |||
return new byte[][] { data, protectedAt }; | |||
} | |||
private BigInteger processDSASecretKey(InputStream inputStream, | |||
BigInteger p, BigInteger q, BigInteger g, BigInteger y, | |||
PBEProtectionRemoverFactory keyProtectionRemoverFactory) | |||
throws IOException, PGPException { | |||
String type; | |||
byte[][] basicData = extractData(inputStream, | |||
keyProtectionRemoverFactory); | |||
// JGit modification: handle unencrypted DSA keys | |||
if (basicData == null) { | |||
byte[] nBytes = SXprUtils.readBytes(inputStream, | |||
inputStream.read()); | |||
BigInteger x = new BigInteger(1, nBytes); | |||
SXprUtils.skipCloseParenthesis(inputStream); | |||
return x; | |||
} | |||
byte[] keyData = basicData[0]; | |||
byte[] protectedAt = basicData[1]; | |||
// | |||
// parse the secret key S-expr | |||
// | |||
InputStream keyIn = new ByteArrayInputStream(keyData); | |||
SXprUtils.skipOpenParenthesis(keyIn); | |||
SXprUtils.skipOpenParenthesis(keyIn); | |||
BigInteger x = readBigInteger("x", keyIn); | |||
SXprUtils.skipCloseParenthesis(keyIn); | |||
// JGit modification: OCB-encrypted keys don't have and don't need a | |||
// hash | |||
if (keyProtectionRemoverFactory instanceof OCBPBEProtectionRemoverFactory) { | |||
return x; | |||
} | |||
SXprUtils.skipOpenParenthesis(keyIn); | |||
type = SXprUtils.readString(keyIn, keyIn.read()); | |||
if (!type.equals("hash")) { | |||
throw new PGPException("hash keyword expected"); | |||
} | |||
type = SXprUtils.readString(keyIn, keyIn.read()); | |||
if (!type.equals("sha1")) { | |||
throw new PGPException("hash keyword expected"); | |||
} | |||
byte[] hashBytes = SXprUtils.readBytes(keyIn, keyIn.read()); | |||
SXprUtils.skipCloseParenthesis(keyIn); | |||
if (digestProvider != null) { | |||
PGPDigestCalculator digestCalculator = digestProvider | |||
.get(HashAlgorithmTags.SHA1); | |||
OutputStream dOut = digestCalculator.getOutputStream(); | |||
dOut.write(Strings.toByteArray("(3:dsa")); | |||
writeCanonical(dOut, "p", p); | |||
writeCanonical(dOut, "q", q); | |||
writeCanonical(dOut, "g", g); | |||
writeCanonical(dOut, "y", y); | |||
writeCanonical(dOut, "x", x); | |||
// check protected-at | |||
if (protectedAt != null) { | |||
dOut.write(protectedAt); | |||
} | |||
dOut.write(Strings.toByteArray(")")); | |||
byte[] check = digestCalculator.getDigest(); | |||
if (!Arrays.constantTimeAreEqual(check, hashBytes)) { | |||
throw new PGPException( | |||
"checksum on protected data failed in SExpr"); | |||
} | |||
} | |||
return x; | |||
} | |||
private BigInteger processElGamalSecretKey(InputStream inputStream, | |||
BigInteger p, BigInteger g, BigInteger y, | |||
PBEProtectionRemoverFactory keyProtectionRemoverFactory) | |||
throws IOException, PGPException { | |||
String type; | |||
byte[][] basicData = extractData(inputStream, | |||
keyProtectionRemoverFactory); | |||
// JGit modification: handle unencrypted EC keys | |||
if (basicData == null) { | |||
byte[] nBytes = SXprUtils.readBytes(inputStream, | |||
inputStream.read()); | |||
BigInteger x = new BigInteger(1, nBytes); | |||
SXprUtils.skipCloseParenthesis(inputStream); | |||
return x; | |||
} | |||
byte[] keyData = basicData[0]; | |||
byte[] protectedAt = basicData[1]; | |||
// | |||
// parse the secret key S-expr | |||
// | |||
InputStream keyIn = new ByteArrayInputStream(keyData); | |||
SXprUtils.skipOpenParenthesis(keyIn); | |||
SXprUtils.skipOpenParenthesis(keyIn); | |||
BigInteger x = readBigInteger("x", keyIn); | |||
SXprUtils.skipCloseParenthesis(keyIn); | |||
// JGit modification: OCB-encrypted keys don't have and don't need a | |||
// hash | |||
if (keyProtectionRemoverFactory instanceof OCBPBEProtectionRemoverFactory) { | |||
return x; | |||
} | |||
SXprUtils.skipOpenParenthesis(keyIn); | |||
type = SXprUtils.readString(keyIn, keyIn.read()); | |||
if (!type.equals("hash")) { | |||
throw new PGPException("hash keyword expected"); | |||
} | |||
type = SXprUtils.readString(keyIn, keyIn.read()); | |||
if (!type.equals("sha1")) { | |||
throw new PGPException("hash keyword expected"); | |||
} | |||
byte[] hashBytes = SXprUtils.readBytes(keyIn, keyIn.read()); | |||
SXprUtils.skipCloseParenthesis(keyIn); | |||
if (digestProvider != null) { | |||
PGPDigestCalculator digestCalculator = digestProvider | |||
.get(HashAlgorithmTags.SHA1); | |||
OutputStream dOut = digestCalculator.getOutputStream(); | |||
dOut.write(Strings.toByteArray("(3:elg")); | |||
writeCanonical(dOut, "p", p); | |||
writeCanonical(dOut, "g", g); | |||
writeCanonical(dOut, "y", y); | |||
writeCanonical(dOut, "x", x); | |||
// check protected-at | |||
if (protectedAt != null) { | |||
dOut.write(protectedAt); | |||
} | |||
dOut.write(Strings.toByteArray(")")); | |||
byte[] check = digestCalculator.getDigest(); | |||
if (!Arrays.constantTimeAreEqual(check, hashBytes)) { | |||
throw new PGPException( | |||
"checksum on protected data failed in SExpr"); | |||
} | |||
} | |||
return x; | |||
} | |||
private BigInteger processECSecretKey(InputStream inputStream, | |||
String curveID, String curveName, byte[] qVal, | |||
PBEProtectionRemoverFactory keyProtectionRemoverFactory) | |||
throws IOException, PGPException { | |||
String type; | |||
byte[][] basicData = extractData(inputStream, | |||
keyProtectionRemoverFactory); | |||
// JGit modification: handle unencrypted EC keys | |||
if (basicData == null) { | |||
byte[] nBytes = SXprUtils.readBytes(inputStream, | |||
inputStream.read()); | |||
BigInteger d = new BigInteger(1, nBytes); | |||
SXprUtils.skipCloseParenthesis(inputStream); | |||
return d; | |||
} | |||
byte[] keyData = basicData[0]; | |||
byte[] protectedAt = basicData[1]; | |||
// | |||
// parse the secret key S-expr | |||
// | |||
InputStream keyIn = new ByteArrayInputStream(keyData); | |||
SXprUtils.skipOpenParenthesis(keyIn); | |||
SXprUtils.skipOpenParenthesis(keyIn); | |||
BigInteger d = readBigInteger("d", keyIn); | |||
SXprUtils.skipCloseParenthesis(keyIn); | |||
// JGit modification: OCB-encrypted keys don't have and don't need a | |||
// hash | |||
if (keyProtectionRemoverFactory instanceof OCBPBEProtectionRemoverFactory) { | |||
return d; | |||
} | |||
SXprUtils.skipOpenParenthesis(keyIn); | |||
type = SXprUtils.readString(keyIn, keyIn.read()); | |||
if (!type.equals("hash")) { | |||
throw new PGPException("hash keyword expected"); | |||
} | |||
type = SXprUtils.readString(keyIn, keyIn.read()); | |||
if (!type.equals("sha1")) { | |||
throw new PGPException("hash keyword expected"); | |||
} | |||
byte[] hashBytes = SXprUtils.readBytes(keyIn, keyIn.read()); | |||
SXprUtils.skipCloseParenthesis(keyIn); | |||
if (digestProvider != null) { | |||
PGPDigestCalculator digestCalculator = digestProvider | |||
.get(HashAlgorithmTags.SHA1); | |||
OutputStream dOut = digestCalculator.getOutputStream(); | |||
dOut.write(Strings.toByteArray("(3:ecc")); | |||
dOut.write(Strings.toByteArray("(" + curveID.length() + ":" | |||
+ curveID + curveName.length() + ":" + curveName + ")")); | |||
writeCanonical(dOut, "q", qVal); | |||
writeCanonical(dOut, "d", d); | |||
// check protected-at | |||
if (protectedAt != null) { | |||
dOut.write(protectedAt); | |||
} | |||
dOut.write(Strings.toByteArray(")")); | |||
byte[] check = digestCalculator.getDigest(); | |||
if (!Arrays.constantTimeAreEqual(check, hashBytes)) { | |||
throw new PGPException( | |||
"checksum on protected data failed in SExpr"); | |||
} | |||
} | |||
return d; | |||
} | |||
private BigInteger[] processRSASecretKey(InputStream inputStream, | |||
BigInteger n, BigInteger e, | |||
PBEProtectionRemoverFactory keyProtectionRemoverFactory) | |||
throws IOException, PGPException { | |||
String type; | |||
byte[][] basicData = extractData(inputStream, | |||
keyProtectionRemoverFactory); | |||
byte[] keyData; | |||
byte[] protectedAt = null; | |||
InputStream keyIn; | |||
BigInteger d; | |||
if (basicData == null) { | |||
keyIn = inputStream; | |||
byte[] nBytes = SXprUtils.readBytes(inputStream, | |||
inputStream.read()); | |||
d = new BigInteger(1, nBytes); | |||
SXprUtils.skipCloseParenthesis(inputStream); | |||
} else { | |||
keyData = basicData[0]; | |||
protectedAt = basicData[1]; | |||
keyIn = new ByteArrayInputStream(keyData); | |||
SXprUtils.skipOpenParenthesis(keyIn); | |||
SXprUtils.skipOpenParenthesis(keyIn); | |||
d = readBigInteger("d", keyIn); | |||
} | |||
// | |||
// parse the secret key S-expr | |||
// | |||
BigInteger p = readBigInteger("p", keyIn); | |||
BigInteger q = readBigInteger("q", keyIn); | |||
BigInteger u = readBigInteger("u", keyIn); | |||
// JGit modification: OCB-encrypted keys don't have and don't need a | |||
// hash | |||
if (basicData == null | |||
|| keyProtectionRemoverFactory instanceof OCBPBEProtectionRemoverFactory) { | |||
return new BigInteger[] { d, p, q, u }; | |||
} | |||
SXprUtils.skipCloseParenthesis(keyIn); | |||
SXprUtils.skipOpenParenthesis(keyIn); | |||
type = SXprUtils.readString(keyIn, keyIn.read()); | |||
if (!type.equals("hash")) { | |||
throw new PGPException("hash keyword expected"); | |||
} | |||
type = SXprUtils.readString(keyIn, keyIn.read()); | |||
if (!type.equals("sha1")) { | |||
throw new PGPException("hash keyword expected"); | |||
} | |||
byte[] hashBytes = SXprUtils.readBytes(keyIn, keyIn.read()); | |||
SXprUtils.skipCloseParenthesis(keyIn); | |||
if (digestProvider != null) { | |||
PGPDigestCalculator digestCalculator = digestProvider | |||
.get(HashAlgorithmTags.SHA1); | |||
OutputStream dOut = digestCalculator.getOutputStream(); | |||
dOut.write(Strings.toByteArray("(3:rsa")); | |||
writeCanonical(dOut, "n", n); | |||
writeCanonical(dOut, "e", e); | |||
writeCanonical(dOut, "d", d); | |||
writeCanonical(dOut, "p", p); | |||
writeCanonical(dOut, "q", q); | |||
writeCanonical(dOut, "u", u); | |||
// check protected-at | |||
if (protectedAt != null) { | |||
dOut.write(protectedAt); | |||
} | |||
dOut.write(Strings.toByteArray(")")); | |||
byte[] check = digestCalculator.getDigest(); | |||
if (!Arrays.constantTimeAreEqual(check, hashBytes)) { | |||
throw new PGPException( | |||
"checksum on protected data failed in SExpr"); | |||
} | |||
} | |||
return new BigInteger[] { d, p, q, u }; | |||
} | |||
private void writeCanonical(OutputStream dOut, String label, BigInteger i) | |||
throws IOException { | |||
writeCanonical(dOut, label, i.toByteArray()); | |||
} | |||
private void writeCanonical(OutputStream dOut, String label, byte[] data) | |||
throws IOException { | |||
dOut.write(Strings.toByteArray( | |||
"(" + label.length() + ":" + label + data.length + ":")); | |||
dOut.write(data); | |||
dOut.write(Strings.toByteArray(")")); | |||
} | |||
} |
@@ -0,0 +1,110 @@ | |||
/* | |||
* Copyright (c) 2000-2021 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org) | |||
* <p> | |||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software | |||
* and associated documentation files (the "Software"), to deal in the Software without restriction, | |||
*including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, | |||
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, | |||
* subject to the following conditions: | |||
* </p> | |||
* <p> | |||
* The above copyright notice and this permission notice shall be included in all copies or substantial | |||
* portions of the Software. | |||
* </p> | |||
* <p> | |||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, | |||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR | |||
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | |||
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |||
* DEALINGS IN THE SOFTWARE. | |||
* </p> | |||
*/ | |||
package org.eclipse.jgit.gpg.bc.internal.keys; | |||
// This class is an unmodified copy from Bouncy Castle; needed because it's package-visible only and used by SExprParser. | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import org.bouncycastle.bcpg.HashAlgorithmTags; | |||
import org.bouncycastle.bcpg.S2K; | |||
import org.bouncycastle.util.io.Streams; | |||
/** | |||
* Utility functions for looking a S-expression keys. This class will move when | |||
* it finds a better home! | |||
* <p> | |||
* Format documented here: | |||
* http://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=agent/keyformat.txt;h=42c4b1f06faf1bbe71ffadc2fee0fad6bec91a97;hb=refs/heads/master | |||
* </p> | |||
*/ | |||
class SXprUtils { | |||
private static int readLength(InputStream in, int ch) throws IOException { | |||
int len = ch - '0'; | |||
while ((ch = in.read()) >= 0 && ch != ':') { | |||
len = len * 10 + ch - '0'; | |||
} | |||
return len; | |||
} | |||
static String readString(InputStream in, int ch) throws IOException { | |||
int len = readLength(in, ch); | |||
char[] chars = new char[len]; | |||
for (int i = 0; i != chars.length; i++) { | |||
chars[i] = (char) in.read(); | |||
} | |||
return new String(chars); | |||
} | |||
static byte[] readBytes(InputStream in, int ch) throws IOException { | |||
int len = readLength(in, ch); | |||
byte[] data = new byte[len]; | |||
Streams.readFully(in, data); | |||
return data; | |||
} | |||
static S2K parseS2K(InputStream in) throws IOException { | |||
skipOpenParenthesis(in); | |||
// Algorithm is hard-coded to SHA1 below anyway. | |||
readString(in, in.read()); | |||
byte[] iv = readBytes(in, in.read()); | |||
final long iterationCount = Long.parseLong(readString(in, in.read())); | |||
skipCloseParenthesis(in); | |||
// we have to return the actual iteration count provided. | |||
S2K s2k = new S2K(HashAlgorithmTags.SHA1, iv, (int) iterationCount) { | |||
@Override | |||
public long getIterationCount() { | |||
return iterationCount; | |||
} | |||
}; | |||
return s2k; | |||
} | |||
static void skipOpenParenthesis(InputStream in) throws IOException { | |||
int ch = in.read(); | |||
if (ch != '(') { | |||
throw new IOException( | |||
"unknown character encountered: " + (char) ch); //$NON-NLS-1$ | |||
} | |||
} | |||
static void skipCloseParenthesis(InputStream in) throws IOException { | |||
int ch = in.read(); | |||
if (ch != ')') { | |||
throw new IOException("unknown character encountered"); //$NON-NLS-1$ | |||
} | |||
} | |||
} |
@@ -0,0 +1,597 @@ | |||
/* | |||
* Copyright (C) 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others | |||
* | |||
* This program and the accompanying materials are made available under the | |||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||
* https://www.eclipse.org/org/documents/edl-v10.php. | |||
* | |||
* SPDX-License-Identifier: BSD-3-Clause | |||
*/ | |||
package org.eclipse.jgit.gpg.bc.internal.keys; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.EOFException; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.StreamCorruptedException; | |||
import java.net.URISyntaxException; | |||
import java.nio.charset.StandardCharsets; | |||
import java.text.MessageFormat; | |||
import java.util.Arrays; | |||
import org.bouncycastle.openpgp.PGPException; | |||
import org.bouncycastle.openpgp.PGPPublicKey; | |||
import org.bouncycastle.openpgp.PGPSecretKey; | |||
import org.bouncycastle.openpgp.operator.PBEProtectionRemoverFactory; | |||
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; | |||
import org.bouncycastle.openpgp.operator.jcajce.JcePBEProtectionRemoverFactory; | |||
import org.bouncycastle.util.io.Streams; | |||
import org.eclipse.jgit.api.errors.CanceledException; | |||
import org.eclipse.jgit.errors.UnsupportedCredentialItem; | |||
import org.eclipse.jgit.gpg.bc.internal.BCText; | |||
import org.eclipse.jgit.util.RawParseUtils; | |||
/** | |||
* Utilities for reading GPG secret keys from a gpg-agent key file. | |||
*/ | |||
public final class SecretKeys { | |||
private SecretKeys() { | |||
// No instantiation. | |||
} | |||
/** | |||
* Something that can supply a passphrase to decrypt an encrypted secret | |||
* key. | |||
*/ | |||
public interface PassphraseSupplier { | |||
/** | |||
* Supplies a passphrase. | |||
* | |||
* @return the passphrase | |||
* @throws PGPException | |||
* if no passphrase can be obtained | |||
* @throws CanceledException | |||
* if the user canceled passphrase entry | |||
* @throws UnsupportedCredentialItem | |||
* if an internal error occurred | |||
* @throws URISyntaxException | |||
* if an internal error occurred | |||
*/ | |||
char[] getPassphrase() throws PGPException, CanceledException, | |||
UnsupportedCredentialItem, URISyntaxException; | |||
} | |||
private static final byte[] PROTECTED_KEY = "protected-private-key" //$NON-NLS-1$ | |||
.getBytes(StandardCharsets.US_ASCII); | |||
private static final byte[] OCB_PROTECTED = "openpgp-s2k3-ocb-aes" //$NON-NLS-1$ | |||
.getBytes(StandardCharsets.US_ASCII); | |||
/** | |||
* Reads a GPG secret key from the given stream. | |||
* | |||
* @param in | |||
* {@link InputStream} to read from, doesn't need to be buffered | |||
* @param calculatorProvider | |||
* for checking digests | |||
* @param passphraseSupplier | |||
* for decrypting encrypted keys | |||
* @param publicKey | |||
* the secret key should be for | |||
* @return the secret key | |||
* @throws IOException | |||
* if the stream cannot be parsed | |||
* @throws PGPException | |||
* if thrown by the underlying S-Expression parser, for instance | |||
* when the passphrase is wrong | |||
* @throws CanceledException | |||
* if thrown by the {@code passphraseSupplier} | |||
* @throws UnsupportedCredentialItem | |||
* if thrown by the {@code passphraseSupplier} | |||
* @throws URISyntaxException | |||
* if thrown by the {@code passphraseSupplier} | |||
*/ | |||
public static PGPSecretKey readSecretKey(InputStream in, | |||
PGPDigestCalculatorProvider calculatorProvider, | |||
PassphraseSupplier passphraseSupplier, PGPPublicKey publicKey) | |||
throws IOException, PGPException, CanceledException, | |||
UnsupportedCredentialItem, URISyntaxException { | |||
byte[] data = Streams.readAll(in); | |||
if (data.length == 0) { | |||
throw new EOFException(); | |||
} else if (data.length < 4 + PROTECTED_KEY.length) { | |||
// +4 for "(21:" for a binary protected key | |||
throw new IOException( | |||
MessageFormat.format(BCText.get().secretKeyTooShort, | |||
Integer.toUnsignedString(data.length))); | |||
} | |||
SExprParser parser = new SExprParser(calculatorProvider); | |||
byte firstChar = data[0]; | |||
try { | |||
if (firstChar == '(') { | |||
// Binary format. | |||
PBEProtectionRemoverFactory decryptor = null; | |||
if (matches(data, 4, PROTECTED_KEY)) { | |||
// AES/CBC encrypted. | |||
decryptor = new JcePBEProtectionRemoverFactory( | |||
passphraseSupplier.getPassphrase(), | |||
calculatorProvider); | |||
} | |||
try (InputStream sIn = new ByteArrayInputStream(data)) { | |||
return parser.parseSecretKey(sIn, decryptor, publicKey); | |||
} | |||
} | |||
// Assume it's the new key-value format. | |||
try (ByteArrayInputStream keyIn = new ByteArrayInputStream(data)) { | |||
byte[] rawData = keyFromNameValueFormat(keyIn); | |||
if (!matches(rawData, 1, PROTECTED_KEY)) { | |||
// Not encrypted human-readable format. | |||
try (InputStream sIn = new ByteArrayInputStream( | |||
convertSexpression(rawData))) { | |||
return parser.parseSecretKey(sIn, null, publicKey); | |||
} | |||
} | |||
// An encrypted key from a key-value file. Most likely AES/OCB | |||
// encrypted. | |||
boolean isOCB[] = { false }; | |||
byte[] sExp = convertSexpression(rawData, isOCB); | |||
PBEProtectionRemoverFactory decryptor; | |||
if (isOCB[0]) { | |||
decryptor = new OCBPBEProtectionRemoverFactory( | |||
passphraseSupplier.getPassphrase(), | |||
calculatorProvider, getAad(sExp)); | |||
} else { | |||
decryptor = new JcePBEProtectionRemoverFactory( | |||
passphraseSupplier.getPassphrase(), | |||
calculatorProvider); | |||
} | |||
try (InputStream sIn = new ByteArrayInputStream(sExp)) { | |||
return parser.parseSecretKey(sIn, decryptor, publicKey); | |||
} | |||
} | |||
} catch (IOException e) { | |||
throw new PGPException(e.getLocalizedMessage(), e); | |||
} | |||
} | |||
/** | |||
* Extract the AAD for the OCB decryption from an s-expression. | |||
* | |||
* @param sExp | |||
* buffer containing a valid binary s-expression | |||
* @return the AAD | |||
*/ | |||
private static byte[] getAad(byte[] sExp) { | |||
// Given a key | |||
// @formatter:off | |||
// (protected-private-key (rsa ... (protected openpgp-s2k3-ocb-aes ... )(protected-at ...))) | |||
// A B C D | |||
// The AAD is [A..B)[C..D). (From the binary serialized form.) | |||
// @formatter:on | |||
int i = 1; // Skip initial '(' | |||
while (sExp[i] != '(') { | |||
i++; | |||
} | |||
int aadStart = i++; | |||
int aadEnd = skip(sExp, aadStart); | |||
byte[] protectedPrefix = "(9:protected" //$NON-NLS-1$ | |||
.getBytes(StandardCharsets.US_ASCII); | |||
while (!matches(sExp, i, protectedPrefix)) { | |||
i++; | |||
} | |||
int protectedStart = i; | |||
int protectedEnd = skip(sExp, protectedStart); | |||
byte[] aadData = new byte[aadEnd - aadStart | |||
- (protectedEnd - protectedStart)]; | |||
System.arraycopy(sExp, aadStart, aadData, 0, protectedStart - aadStart); | |||
System.arraycopy(sExp, protectedEnd, aadData, protectedStart - aadStart, | |||
aadEnd - protectedEnd); | |||
return aadData; | |||
} | |||
/** | |||
* Skips a list including nested lists. | |||
* | |||
* @param sExp | |||
* buffer containing valid binary s-expression data | |||
* @param start | |||
* index of the opening '(' of the list to skip | |||
* @return the index after the closing ')' of the skipped list | |||
*/ | |||
private static int skip(byte[] sExp, int start) { | |||
int i = start + 1; | |||
int depth = 1; | |||
while (depth > 0) { | |||
switch (sExp[i]) { | |||
case '(': | |||
depth++; | |||
break; | |||
case ')': | |||
depth--; | |||
break; | |||
default: | |||
// We must be on a length | |||
int j = i; | |||
while (sExp[j] >= '0' && sExp[j] <= '9') { | |||
j++; | |||
} | |||
// j is on the colon | |||
int length = Integer.parseInt( | |||
new String(sExp, i, j - i, StandardCharsets.US_ASCII)); | |||
i = j + length; | |||
} | |||
i++; | |||
} | |||
return i; | |||
} | |||
/** | |||
* Checks whether the {@code needle} matches {@code src} at offset | |||
* {@code from}. | |||
* | |||
* @param src | |||
* to match against {@code needle} | |||
* @param from | |||
* position in {@code src} to start matching | |||
* @param needle | |||
* to match against | |||
* @return {@code true} if {@code src} contains {@code needle} at position | |||
* {@code from}, {@code false} otherwise | |||
*/ | |||
private static boolean matches(byte[] src, int from, byte[] needle) { | |||
if (from < 0 || from + needle.length > src.length) { | |||
return false; | |||
} | |||
return org.bouncycastle.util.Arrays.constantTimeAreEqual(needle.length, | |||
src, from, needle, 0); | |||
} | |||
/** | |||
* Converts a human-readable serialized s-expression into a binary | |||
* serialized s-expression. | |||
* | |||
* @param humanForm | |||
* to convert | |||
* @return the converted s-expression | |||
* @throws IOException | |||
* if the conversion fails | |||
*/ | |||
private static byte[] convertSexpression(byte[] humanForm) | |||
throws IOException { | |||
boolean[] isOCB = { false }; | |||
return convertSexpression(humanForm, isOCB); | |||
} | |||
/** | |||
* Converts a human-readable serialized s-expression into a binary | |||
* serialized s-expression. | |||
* | |||
* @param humanForm | |||
* to convert | |||
* @param isOCB | |||
* returns whether the s-expression specified AES/OCB encryption | |||
* @return the converted s-expression | |||
* @throws IOException | |||
* if the conversion fails | |||
*/ | |||
private static byte[] convertSexpression(byte[] humanForm, boolean[] isOCB) | |||
throws IOException { | |||
int pos = 0; | |||
try (ByteArrayOutputStream out = new ByteArrayOutputStream( | |||
humanForm.length)) { | |||
while (pos < humanForm.length) { | |||
byte b = humanForm[pos]; | |||
if (b == '(' || b == ')') { | |||
out.write(b); | |||
pos++; | |||
} else if (isGpgSpace(b)) { | |||
pos++; | |||
} else if (b == '#') { | |||
// Hex value follows up to the next # | |||
int i = ++pos; | |||
while (i < humanForm.length && isHex(humanForm[i])) { | |||
i++; | |||
} | |||
if (i == pos || humanForm[i] != '#') { | |||
throw new StreamCorruptedException( | |||
BCText.get().sexprHexNotClosed); | |||
} | |||
if ((i - pos) % 2 != 0) { | |||
throw new StreamCorruptedException( | |||
BCText.get().sexprHexOdd); | |||
} | |||
int l = (i - pos) / 2; | |||
out.write(Integer.toString(l) | |||
.getBytes(StandardCharsets.US_ASCII)); | |||
out.write(':'); | |||
while (pos < i) { | |||
int x = (nibble(humanForm[pos]) << 4) | |||
| nibble(humanForm[pos + 1]); | |||
pos += 2; | |||
out.write(x); | |||
} | |||
pos = i + 1; | |||
} else if (isTokenChar(b)) { | |||
// Scan the token | |||
int start = pos++; | |||
while (pos < humanForm.length | |||
&& isTokenChar(humanForm[pos])) { | |||
pos++; | |||
} | |||
int l = pos - start; | |||
if (pos - start == OCB_PROTECTED.length | |||
&& matches(humanForm, start, OCB_PROTECTED)) { | |||
isOCB[0] = true; | |||
} | |||
out.write(Integer.toString(l) | |||
.getBytes(StandardCharsets.US_ASCII)); | |||
out.write(':'); | |||
out.write(humanForm, start, pos - start); | |||
} else if (b == '"') { | |||
// Potentially quoted string. | |||
int start = ++pos; | |||
boolean escaped = false; | |||
while (pos < humanForm.length | |||
&& (escaped || humanForm[pos] != '"')) { | |||
int ch = humanForm[pos++]; | |||
escaped = !escaped && ch == '\\'; | |||
} | |||
if (pos >= humanForm.length) { | |||
throw new StreamCorruptedException( | |||
BCText.get().sexprStringNotClosed); | |||
} | |||
// start is on the first character of the string, pos on the | |||
// closing quote. | |||
byte[] dq = dequote(humanForm, start, pos); | |||
out.write(Integer.toString(dq.length) | |||
.getBytes(StandardCharsets.US_ASCII)); | |||
out.write(':'); | |||
out.write(dq); | |||
pos++; | |||
} else { | |||
throw new StreamCorruptedException( | |||
MessageFormat.format(BCText.get().sexprUnhandled, | |||
Integer.toHexString(b & 0xFF))); | |||
} | |||
} | |||
return out.toByteArray(); | |||
} | |||
} | |||
/** | |||
* GPG-style string de-quoting, which is basically C-style, with some | |||
* literal CR/LF escaping. | |||
* | |||
* @param in | |||
* buffer containing the quoted string | |||
* @param from | |||
* index after the opening quote in {@code in} | |||
* @param to | |||
* index of the closing quote in {@code in} | |||
* @return the dequoted raw string value | |||
* @throws StreamCorruptedException | |||
*/ | |||
private static byte[] dequote(byte[] in, int from, int to) | |||
throws StreamCorruptedException { | |||
// Result must be shorter or have the same length | |||
byte[] out = new byte[to - from]; | |||
int j = 0; | |||
int i = from; | |||
while (i < to) { | |||
byte b = in[i++]; | |||
if (b != '\\') { | |||
out[j++] = b; | |||
continue; | |||
} | |||
if (i == to) { | |||
throw new StreamCorruptedException( | |||
BCText.get().sexprStringInvalidEscapeAtEnd); | |||
} | |||
b = in[i++]; | |||
switch (b) { | |||
case 'b': | |||
out[j++] = '\b'; | |||
break; | |||
case 'f': | |||
out[j++] = '\f'; | |||
break; | |||
case 'n': | |||
out[j++] = '\n'; | |||
break; | |||
case 'r': | |||
out[j++] = '\r'; | |||
break; | |||
case 't': | |||
out[j++] = '\t'; | |||
break; | |||
case 'v': | |||
out[j++] = 0x0B; | |||
break; | |||
case '"': | |||
case '\'': | |||
case '\\': | |||
out[j++] = b; | |||
break; | |||
case '\r': | |||
// Escaped literal line end. If an LF is following, skip that, | |||
// too. | |||
if (i < to && in[i] == '\n') { | |||
i++; | |||
} | |||
break; | |||
case '\n': | |||
// Same for LF possibly followed by CR. | |||
if (i < to && in[i] == '\r') { | |||
i++; | |||
} | |||
break; | |||
case 'x': | |||
if (i + 1 >= to || !isHex(in[i]) || !isHex(in[i + 1])) { | |||
throw new StreamCorruptedException( | |||
BCText.get().sexprStringInvalidHexEscape); | |||
} | |||
out[j++] = (byte) ((nibble(in[i]) << 4) | nibble(in[i + 1])); | |||
i += 2; | |||
break; | |||
case '0': | |||
case '1': | |||
case '2': | |||
case '3': | |||
if (i + 2 >= to || !isOctal(in[i]) || !isOctal(in[i + 1]) | |||
|| !isOctal(in[i + 2])) { | |||
throw new StreamCorruptedException( | |||
BCText.get().sexprStringInvalidOctalEscape); | |||
} | |||
out[j++] = (byte) (((((in[i] - '0') << 3) | |||
| (in[i + 1] - '0')) << 3) | (in[i + 2] - '0')); | |||
i += 3; | |||
break; | |||
default: | |||
throw new StreamCorruptedException(MessageFormat.format( | |||
BCText.get().sexprStringInvalidEscape, | |||
Integer.toHexString(b & 0xFF))); | |||
} | |||
} | |||
return Arrays.copyOf(out, j); | |||
} | |||
/** | |||
* Extracts the key from a GPG name-value-pair key file. | |||
* <p> | |||
* Package-visible for tests only. | |||
* </p> | |||
* | |||
* @param in | |||
* {@link InputStream} to read from; should be buffered | |||
* @return the raw key data as extracted from the file | |||
* @throws IOException | |||
* if the {@code in} stream cannot be read or does not contain a | |||
* key | |||
*/ | |||
static byte[] keyFromNameValueFormat(InputStream in) throws IOException { | |||
// It would be nice if we could use RawParseUtils here, but GPG compares | |||
// names case-insensitively. We're only interested in the "Key:" | |||
// name-value pair. | |||
int[] nameLow = { 'k', 'e', 'y', ':' }; | |||
int[] nameCap = { 'K', 'E', 'Y', ':' }; | |||
int nameIdx = 0; | |||
for (;;) { | |||
int next = in.read(); | |||
if (next < 0) { | |||
throw new EOFException(); | |||
} | |||
if (next == '\n') { | |||
nameIdx = 0; | |||
} else if (nameIdx >= 0) { | |||
if (nameLow[nameIdx] == next || nameCap[nameIdx] == next) { | |||
nameIdx++; | |||
if (nameIdx == nameLow.length) { | |||
break; | |||
} | |||
} else { | |||
nameIdx = -1; | |||
} | |||
} | |||
} | |||
// We're after "Key:". Read the value as continuation lines. | |||
int last = ':'; | |||
byte[] rawData; | |||
try (ByteArrayOutputStream out = new ByteArrayOutputStream(8192)) { | |||
for (;;) { | |||
int next = in.read(); | |||
if (next < 0) { | |||
break; | |||
} | |||
if (last == '\n') { | |||
if (next == ' ' || next == '\t') { | |||
// Continuation line; skip this whitespace | |||
last = next; | |||
continue; | |||
} | |||
break; // Not a continuation line | |||
} | |||
out.write(next); | |||
last = next; | |||
} | |||
rawData = out.toByteArray(); | |||
} | |||
// GPG trims off trailing whitespace, and a line having only whitespace | |||
// is a single LF. | |||
try (ByteArrayOutputStream out = new ByteArrayOutputStream( | |||
rawData.length)) { | |||
int lineStart = 0; | |||
boolean trimLeading = true; | |||
while (lineStart < rawData.length) { | |||
int nextLineStart = RawParseUtils.nextLF(rawData, lineStart); | |||
if (trimLeading) { | |||
while (lineStart < nextLineStart | |||
&& isGpgSpace(rawData[lineStart])) { | |||
lineStart++; | |||
} | |||
} | |||
// Trim trailing | |||
int i = nextLineStart - 1; | |||
while (lineStart < i && isGpgSpace(rawData[i])) { | |||
i--; | |||
} | |||
if (i <= lineStart) { | |||
// Empty line signifies LF | |||
out.write('\n'); | |||
trimLeading = true; | |||
} else { | |||
out.write(rawData, lineStart, i - lineStart + 1); | |||
trimLeading = false; | |||
} | |||
lineStart = nextLineStart; | |||
} | |||
return out.toByteArray(); | |||
} | |||
} | |||
private static boolean isGpgSpace(int ch) { | |||
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'; | |||
} | |||
private static boolean isTokenChar(int ch) { | |||
switch (ch) { | |||
case '-': | |||
case '.': | |||
case '/': | |||
case '_': | |||
case ':': | |||
case '*': | |||
case '+': | |||
case '=': | |||
return true; | |||
default: | |||
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') | |||
|| (ch >= '0' && ch <= '9')) { | |||
return true; | |||
} | |||
return false; | |||
} | |||
} | |||
private static boolean isHex(int ch) { | |||
return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') | |||
|| (ch >= 'a' && ch <= 'f'); | |||
} | |||
private static boolean isOctal(int ch) { | |||
return (ch >= '0' && ch <= '7'); | |||
} | |||
private static int nibble(int ch) { | |||
if (ch >= '0' && ch <= '9') { | |||
return ch - '0'; | |||
} else if (ch >= 'A' && ch <= 'F') { | |||
return ch - 'A' + 10; | |||
} else if (ch >= 'a' && ch <= 'f') { | |||
return ch - 'a' + 10; | |||
} | |||
return -1; | |||
} | |||
} |
@@ -9,20 +9,20 @@ Bundle-Localization: plugin | |||
Bundle-Vendor: %Bundle-Vendor | |||
Bundle-ActivationPolicy: lazy | |||
Import-Package: org.apache.http;version="[4.3.0,5.0.0)", | |||
org.apache.http.client;version="[4.3.0,5.0.0)", | |||
org.apache.http.client.config;version="[4.3.0,5.0.0)", | |||
org.apache.http.client.methods;version="[4.3.0,5.0.0)", | |||
org.apache.http.client.params;version="[4.3.0,5.0.0)", | |||
org.apache.http.client;version="[4.4.0,5.0.0)", | |||
org.apache.http.client.config;version="[4.4.0,5.0.0)", | |||
org.apache.http.client.methods;version="[4.4.0,5.0.0)", | |||
org.apache.http.client.params;version="[4.4.0,5.0.0)", | |||
org.apache.http.config;version="[4.3.0,5.0.0)", | |||
org.apache.http.conn;version="[4.3.0,5.0.0)", | |||
org.apache.http.conn.params;version="[4.3.0,5.0.0)", | |||
org.apache.http.conn.scheme;version="[4.3.0,5.0.0)", | |||
org.apache.http.conn.socket;version="[4.3.0,5.0.0)", | |||
org.apache.http.conn.ssl;version="[4.3.0,5.0.0)", | |||
org.apache.http.conn.util;version="[4.3.0,5.0.0)", | |||
org.apache.http.conn;version="[4.4.0,5.0.0)", | |||
org.apache.http.conn.params;version="[4.4.0,5.0.0)", | |||
org.apache.http.conn.scheme;version="[4.4.0,5.0.0)", | |||
org.apache.http.conn.socket;version="[4.4.0,5.0.0)", | |||
org.apache.http.conn.ssl;version="[4.4.0,5.0.0)", | |||
org.apache.http.conn.util;version="[4.4.0,5.0.0)", | |||
org.apache.http.entity;version="[4.3.0,5.0.0)", | |||
org.apache.http.impl.client;version="[4.3.0,5.0.0)", | |||
org.apache.http.impl.conn;version="[4.3.0,5.0.0)", | |||
org.apache.http.impl.client;version="[4.4.0,5.0.0)", | |||
org.apache.http.impl.conn;version="[4.4.0,5.0.0)", | |||
org.apache.http.params;version="[4.3.0,5.0.0)", | |||
org.apache.http.ssl;version="[4.3.0,5.0.0)", | |||
org.eclipse.jgit.annotations;version="[6.0.0,6.1.0)", |
@@ -1 +1,2 @@ | |||
httpWrongConnectionType=Wrong connection type: expected {0}, got {1}. | |||
unexpectedSSLContextException=unexpected exception when searching for the TLS protocol |
@@ -57,9 +57,7 @@ import org.apache.http.config.Registry; | |||
import org.apache.http.config.RegistryBuilder; | |||
import org.apache.http.conn.socket.ConnectionSocketFactory; | |||
import org.apache.http.conn.socket.PlainConnectionSocketFactory; | |||
import org.apache.http.conn.ssl.DefaultHostnameVerifier; | |||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory; | |||
import org.apache.http.conn.util.PublicSuffixMatcherLoader; | |||
import org.apache.http.impl.client.HttpClientBuilder; | |||
import org.apache.http.impl.client.HttpClients; | |||
import org.apache.http.impl.client.SystemDefaultCredentialsProvider; | |||
@@ -103,7 +101,11 @@ public class HttpClientConnection implements HttpConnection { | |||
private HostnameVerifier hostnameverifier; | |||
SSLContext ctx; | |||
private SSLContext ctx; | |||
private SSLConnectionSocketFactory socketFactory; | |||
private boolean usePooling = true; | |||
private HttpClient getClient() { | |||
if (client == null) { | |||
@@ -125,11 +127,18 @@ public class HttpClientConnection implements HttpConnection { | |||
configBuilder | |||
.setRedirectsEnabled(followRedirects.booleanValue()); | |||
} | |||
SSLConnectionSocketFactory sslConnectionFactory = getSSLSocketFactory(); | |||
boolean pooled = true; | |||
SSLConnectionSocketFactory sslConnectionFactory; | |||
if (socketFactory != null) { | |||
pooled = usePooling; | |||
sslConnectionFactory = socketFactory; | |||
} else { | |||
// Legacy implementation. | |||
pooled = (hostnameverifier == null); | |||
sslConnectionFactory = getSSLSocketFactory(); | |||
} | |||
clientBuilder.setSSLSocketFactory(sslConnectionFactory); | |||
if (hostnameverifier != null) { | |||
// Using a custom verifier: we don't want pooled connections | |||
// with this. | |||
if (!pooled) { | |||
Registry<ConnectionSocketFactory> registry = RegistryBuilder | |||
.<ConnectionSocketFactory> create() | |||
.register("https", sslConnectionFactory) | |||
@@ -147,14 +156,19 @@ public class HttpClientConnection implements HttpConnection { | |||
return client; | |||
} | |||
void setSSLSocketFactory(@NonNull SSLConnectionSocketFactory factory, | |||
boolean isDefault) { | |||
socketFactory = factory; | |||
usePooling = isDefault; | |||
} | |||
private SSLConnectionSocketFactory getSSLSocketFactory() { | |||
HostnameVerifier verifier = hostnameverifier; | |||
SSLContext context; | |||
if (verifier == null) { | |||
// Use defaults | |||
context = SSLContexts.createDefault(); | |||
verifier = new DefaultHostnameVerifier( | |||
PublicSuffixMatcherLoader.getDefault()); | |||
context = SSLContexts.createSystemDefault(); | |||
verifier = SSLConnectionSocketFactory.getDefaultHostnameVerifier(); | |||
} else { | |||
// Using a custom verifier. Attention: configure() must have been | |||
// called already, otherwise one gets a "context not initialized" |
@@ -1,5 +1,5 @@ | |||
/* | |||
* Copyright (C) 2013 Christian Halstrick <christian.halstrick@sap.com> and others | |||
* Copyright (C) 2013, 2020 Christian Halstrick <christian.halstrick@sap.com> and others | |||
* | |||
* This program and the accompanying materials are made available under the | |||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||
@@ -12,27 +12,100 @@ package org.eclipse.jgit.transport.http.apache; | |||
import java.io.IOException; | |||
import java.net.Proxy; | |||
import java.net.URL; | |||
import java.security.GeneralSecurityException; | |||
import java.text.MessageFormat; | |||
import javax.net.ssl.HostnameVerifier; | |||
import javax.net.ssl.SSLContext; | |||
import javax.net.ssl.SSLSocket; | |||
import javax.net.ssl.TrustManager; | |||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory; | |||
import org.eclipse.jgit.transport.http.HttpConnection; | |||
import org.eclipse.jgit.transport.http.HttpConnectionFactory; | |||
import org.eclipse.jgit.transport.http.HttpConnectionFactory2; | |||
import org.eclipse.jgit.transport.http.NoCheckX509TrustManager; | |||
import org.eclipse.jgit.transport.http.apache.internal.HttpApacheText; | |||
import org.eclipse.jgit.util.HttpSupport; | |||
/** | |||
* A factory returning instances of | |||
* {@link org.eclipse.jgit.transport.http.apache.HttpClientConnection} | |||
* A factory returning instances of {@link HttpClientConnection}. | |||
* | |||
* @since 3.3 | |||
*/ | |||
public class HttpClientConnectionFactory implements HttpConnectionFactory { | |||
/** {@inheritDoc} */ | |||
public class HttpClientConnectionFactory implements HttpConnectionFactory2 { | |||
@Override | |||
public HttpConnection create(URL url) throws IOException { | |||
return new HttpClientConnection(url.toString()); | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
public HttpConnection create(URL url, Proxy proxy) | |||
throws IOException { | |||
public HttpConnection create(URL url, Proxy proxy) throws IOException { | |||
return new HttpClientConnection(url.toString(), proxy); | |||
} | |||
@Override | |||
public GitSession newSession() { | |||
return new HttpClientSession(); | |||
} | |||
private static class HttpClientSession implements GitSession { | |||
private SSLContext securityContext; | |||
private SSLConnectionSocketFactory socketFactory; | |||
private boolean isDefault; | |||
@Override | |||
public HttpClientConnection configure(HttpConnection connection, | |||
boolean sslVerify) | |||
throws IOException, GeneralSecurityException { | |||
if (!(connection instanceof HttpClientConnection)) { | |||
throw new IllegalArgumentException(MessageFormat.format( | |||
HttpApacheText.get().httpWrongConnectionType, | |||
HttpClientConnection.class.getName(), | |||
connection.getClass().getName())); | |||
} | |||
HttpClientConnection conn = (HttpClientConnection) connection; | |||
String scheme = conn.getURL().getProtocol(); | |||
if (!"https".equals(scheme)) { //$NON-NLS-1$ | |||
return conn; | |||
} | |||
if (securityContext == null || isDefault != sslVerify) { | |||
isDefault = sslVerify; | |||
HostnameVerifier verifier; | |||
if (sslVerify) { | |||
securityContext = SSLContext.getDefault(); | |||
verifier = SSLConnectionSocketFactory | |||
.getDefaultHostnameVerifier(); | |||
} else { | |||
securityContext = SSLContext.getInstance("TLS"); | |||
TrustManager[] trustAllCerts = { | |||
new NoCheckX509TrustManager() }; | |||
securityContext.init(null, trustAllCerts, null); | |||
verifier = (name, session) -> true; | |||
} | |||
socketFactory = new SSLConnectionSocketFactory(securityContext, | |||
verifier) { | |||
@Override | |||
protected void prepareSocket(SSLSocket socket) | |||
throws IOException { | |||
super.prepareSocket(socket); | |||
HttpSupport.configureTLS(socket); | |||
} | |||
}; | |||
} | |||
conn.setSSLSocketFactory(socketFactory, isDefault); | |||
return conn; | |||
} | |||
@Override | |||
public void close() { | |||
securityContext = null; | |||
socketFactory = null; | |||
} | |||
} | |||
} |
@@ -27,5 +27,6 @@ public class HttpApacheText extends TranslationBundle { | |||
} | |||
// @formatter:off | |||
/***/ public String httpWrongConnectionType; | |||
/***/ public String unexpectedSSLContextException; | |||
} |
@@ -20,7 +20,7 @@ import javax.servlet.http.HttpServletRequest; | |||
import javax.servlet.http.HttpServletResponse; | |||
import org.eclipse.jgit.internal.storage.file.ObjectDirectory; | |||
import org.eclipse.jgit.internal.storage.file.PackFile; | |||
import org.eclipse.jgit.internal.storage.file.Pack; | |||
import org.eclipse.jgit.lib.ObjectDatabase; | |||
/** Sends the current list of pack files, sorted most recent first. */ | |||
@@ -38,7 +38,7 @@ class InfoPacksServlet extends HttpServlet { | |||
final StringBuilder out = new StringBuilder(); | |||
final ObjectDatabase db = getRepository(req).getObjectDatabase(); | |||
if (db instanceof ObjectDirectory) { | |||
for (PackFile pack : ((ObjectDirectory) db).getPacks()) { | |||
for (Pack pack : ((ObjectDirectory) db).getPacks()) { | |||
out.append("P "); | |||
out.append(pack.getPackFile().getName()); | |||
out.append('\n'); |
@@ -51,8 +51,8 @@ org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTags=error | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=no_tag | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTags=ignore | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private |
@@ -12,7 +12,7 @@ Import-Package: javax.servlet;version="[2.5.0,3.2.0)", | |||
org.apache.commons.codec;version="[1.6.0,2.0.0)", | |||
org.apache.commons.codec.binary;version="[1.6.0,2.0.0)", | |||
org.apache.http;version="[4.3.0,5.0.0)", | |||
org.apache.http.client;version="[4.3.0,5.0.0)", | |||
org.apache.http.client;version="[4.4.0,5.0.0)", | |||
org.apache.http.message;version="[4.3.0,5.0.0)", | |||
org.eclipse.jetty.continuation;version="[9.4.5,10.0.0)", | |||
org.eclipse.jetty.http;version="[9.4.5,10.0.0)", | |||
@@ -28,6 +28,7 @@ Import-Package: javax.servlet;version="[2.5.0,3.2.0)", | |||
org.eclipse.jetty.util.log;version="[9.4.5,10.0.0)", | |||
org.eclipse.jetty.util.security;version="[9.4.5,10.0.0)", | |||
org.eclipse.jetty.util.thread;version="[9.4.5,10.0.0)", | |||
org.eclipse.jgit.api;version="[6.0.0,6.1.0)", | |||
org.eclipse.jgit.errors;version="[6.0.0,6.1.0)", | |||
org.eclipse.jgit.http.server;version="[6.0.0,6.1.0)", | |||
org.eclipse.jgit.http.server.glue;version="[6.0.0,6.1.0)", |
@@ -4,3 +4,5 @@ output.. = bin/ | |||
bin.includes = META-INF/,\ | |||
.,\ | |||
plugin.properties | |||
additional.bundles = org.apache.log4j,\ | |||
org.slf4j.binding.log4j12 |
@@ -83,6 +83,17 @@ class RefsUnreadableInMemoryRepository extends InMemoryRepository { | |||
return super.getRefsByPrefix(prefix); | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
public List<Ref> getRefsByPrefixWithExclusions(String include, Set<String> excludes) | |||
throws IOException { | |||
if (failing) { | |||
throw new IOException("disk failed, no refs found"); | |||
} | |||
return super.getRefsByPrefixWithExclusions(include, excludes); | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
public Set<Ref> getTipsWithSha1(ObjectId id) throws IOException { |
@@ -0,0 +1,99 @@ | |||
/* | |||
* Copyright (C) 2020, Thomas Wolf <thomas.wolf@paranor.ch> and others | |||
* | |||
* This program and the accompanying materials are made available under the | |||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||
* https://www.eclipse.org/org/documents/edl-v10.php. | |||
* | |||
* SPDX-License-Identifier: BSD-3-Clause | |||
*/ | |||
package org.eclipse.jgit.http.test; | |||
import java.util.ArrayList; | |||
import java.util.Collection; | |||
import java.util.List; | |||
import org.eclipse.jgit.junit.http.HttpTestCase; | |||
import org.eclipse.jgit.transport.HttpTransport; | |||
import org.eclipse.jgit.transport.http.HttpConnectionFactory; | |||
import org.eclipse.jgit.transport.http.JDKHttpConnectionFactory; | |||
import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory; | |||
import org.junit.AfterClass; | |||
import org.junit.BeforeClass; | |||
import org.junit.Ignore; | |||
import org.junit.runner.RunWith; | |||
import org.junit.runners.Parameterized; | |||
import org.junit.runners.Parameterized.Parameters; | |||
/** | |||
* Abstract test base class for running HTTP-related tests with all connection | |||
* factories provided in JGit and with both protocol V0 and V2. | |||
*/ | |||
@Ignore | |||
@RunWith(Parameterized.class) | |||
public abstract class AllProtocolsHttpTestCase extends HttpTestCase { | |||
protected static class TestParameters { | |||
public final HttpConnectionFactory factory; | |||
public final boolean enableProtocolV2; | |||
public TestParameters(HttpConnectionFactory factory, | |||
boolean enableProtocolV2) { | |||
this.factory = factory; | |||
this.enableProtocolV2 = enableProtocolV2; | |||
} | |||
@Override | |||
public String toString() { | |||
return factory.toString() + " protocol " | |||
+ (enableProtocolV2 ? "V2" : "V0"); | |||
} | |||
} | |||
@Parameters(name = "{0}") | |||
public static Collection<TestParameters> data() { | |||
// run all tests with both connection factories we have | |||
HttpConnectionFactory[] factories = new HttpConnectionFactory[] { | |||
new JDKHttpConnectionFactory() { | |||
@Override | |||
public String toString() { | |||
return this.getClass().getSuperclass().getName(); | |||
} | |||
}, new HttpClientConnectionFactory() { | |||
@Override | |||
public String toString() { | |||
return this.getClass().getSuperclass().getName(); | |||
} | |||
} }; | |||
List<TestParameters> result = new ArrayList<>(); | |||
for (HttpConnectionFactory factory : factories) { | |||
result.add(new TestParameters(factory, false)); | |||
result.add(new TestParameters(factory, true)); | |||
} | |||
return result; | |||
} | |||
protected final boolean enableProtocolV2; | |||
protected AllProtocolsHttpTestCase(TestParameters params) { | |||
HttpTransport.setConnectionFactory(params.factory); | |||
enableProtocolV2 = params.enableProtocolV2; | |||
} | |||
private static HttpConnectionFactory originalFactory; | |||
@BeforeClass | |||
public static void saveConnectionFactory() { | |||
originalFactory = HttpTransport.getConnectionFactory(); | |||
} | |||
@AfterClass | |||
public static void restoreConnectionFactory() { | |||
HttpTransport.setConnectionFactory(originalFactory); | |||
} | |||
} |
@@ -35,6 +35,7 @@ import org.eclipse.jgit.lib.Constants; | |||
import org.eclipse.jgit.lib.NullProgressMonitor; | |||
import org.eclipse.jgit.lib.Ref; | |||
import org.eclipse.jgit.lib.Repository; | |||
import org.eclipse.jgit.lib.StoredConfig; | |||
import org.eclipse.jgit.revwalk.RevBlob; | |||
import org.eclipse.jgit.revwalk.RevCommit; | |||
import org.eclipse.jgit.transport.FetchConnection; | |||
@@ -77,6 +78,9 @@ public class DumbClientDumbServerTest extends AllFactoriesHttpTestCase { | |||
remoteRepository = src.getRepository(); | |||
remoteURI = toURIish(app, srcGit.getName()); | |||
StoredConfig cfg = remoteRepository.getConfig(); | |||
cfg.setInt("protocol", null, "version", 0); | |||
cfg.save(); | |||
A_txt = src.blob("A"); | |||
A = src.commit().add("A_txt", A_txt).create(); |
@@ -35,6 +35,7 @@ import org.eclipse.jgit.lib.Constants; | |||
import org.eclipse.jgit.lib.NullProgressMonitor; | |||
import org.eclipse.jgit.lib.Ref; | |||
import org.eclipse.jgit.lib.Repository; | |||
import org.eclipse.jgit.lib.StoredConfig; | |||
import org.eclipse.jgit.revwalk.RevBlob; | |||
import org.eclipse.jgit.revwalk.RevCommit; | |||
import org.eclipse.jgit.transport.FetchConnection; | |||
@@ -42,11 +43,10 @@ import org.eclipse.jgit.transport.HttpTransport; | |||
import org.eclipse.jgit.transport.Transport; | |||
import org.eclipse.jgit.transport.TransportHttp; | |||
import org.eclipse.jgit.transport.URIish; | |||
import org.eclipse.jgit.transport.http.HttpConnectionFactory; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
public class DumbClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
public class DumbClientSmartServerTest extends AllProtocolsHttpTestCase { | |||
private Repository remoteRepository; | |||
private URIish remoteURI; | |||
@@ -55,8 +55,8 @@ public class DumbClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
private RevCommit A, B; | |||
public DumbClientSmartServerTest(HttpConnectionFactory cf) { | |||
super(cf); | |||
public DumbClientSmartServerTest(TestParameters params) { | |||
super(params); | |||
} | |||
@Override | |||
@@ -76,6 +76,9 @@ public class DumbClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
remoteRepository = src.getRepository(); | |||
remoteURI = toURIish(app, srcName); | |||
StoredConfig cfg = remoteRepository.getConfig(); | |||
cfg.setInt("protocol", null, "version", enableProtocolV2 ? 2 : 0); | |||
cfg.save(); | |||
A_txt = src.blob("A"); | |||
A = src.commit().add("A_txt", A_txt).create(); |
@@ -324,7 +324,22 @@ public class HttpClientTests extends AllFactoriesHttpTestCase { | |||
} | |||
@Test | |||
public void testHttpClientWantsV2ButServerNotConfigured() throws Exception { | |||
public void testHttpClientWantsV2AndServerNotConfigured() throws Exception { | |||
String url = smartAuthNoneURI.toString() + "/info/refs?service=git-upload-pack"; | |||
HttpConnection c = HttpTransport.getConnectionFactory() | |||
.create(new URL(url)); | |||
c.setRequestMethod("GET"); | |||
c.setRequestProperty("Git-Protocol", "version=2"); | |||
assertEquals(200, c.getResponseCode()); | |||
PacketLineIn pckIn = new PacketLineIn(c.getInputStream()); | |||
assertThat(pckIn.readString(), is("version 2")); | |||
} | |||
@Test | |||
public void testHttpServerConfiguredToV0() throws Exception { | |||
remoteRepository.getRepository().getConfig().setInt( | |||
"protocol", null, "version", 0); | |||
String url = smartAuthNoneURI.toString() + "/info/refs?service=git-upload-pack"; | |||
HttpConnection c = HttpTransport.getConnectionFactory() | |||
.create(new URL(url)); | |||
@@ -342,9 +357,6 @@ public class HttpClientTests extends AllFactoriesHttpTestCase { | |||
@Test | |||
public void testV2HttpFirstResponse() throws Exception { | |||
remoteRepository.getRepository().getConfig().setInt( | |||
"protocol", null, "version", 2); | |||
String url = smartAuthNoneURI.toString() + "/info/refs?service=git-upload-pack"; | |||
HttpConnection c = HttpTransport.getConnectionFactory() | |||
.create(new URL(url)); | |||
@@ -364,9 +376,6 @@ public class HttpClientTests extends AllFactoriesHttpTestCase { | |||
@Test | |||
public void testV2HttpSubsequentResponse() throws Exception { | |||
remoteRepository.getRepository().getConfig().setInt( | |||
"protocol", null, "version", 2); | |||
String url = smartAuthNoneURI.toString() + "/git-upload-pack"; | |||
HttpConnection c = HttpTransport.getConnectionFactory() | |||
.create(new URL(url)); |
@@ -1,5 +1,5 @@ | |||
/* | |||
* Copyright (C) 2017 Thomas Wolf <thomas.wolf@paranor.ch> and others | |||
* Copyright (C) 2017, 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others | |||
* | |||
* This program and the accompanying materials are made available under the | |||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||
@@ -41,6 +41,7 @@ import org.eclipse.jgit.junit.http.AppServer; | |||
import org.eclipse.jgit.lib.ConfigConstants; | |||
import org.eclipse.jgit.lib.NullProgressMonitor; | |||
import org.eclipse.jgit.lib.Repository; | |||
import org.eclipse.jgit.lib.StoredConfig; | |||
import org.eclipse.jgit.revwalk.RevBlob; | |||
import org.eclipse.jgit.revwalk.RevCommit; | |||
import org.eclipse.jgit.transport.CredentialItem; | |||
@@ -48,7 +49,6 @@ import org.eclipse.jgit.transport.CredentialsProvider; | |||
import org.eclipse.jgit.transport.Transport; | |||
import org.eclipse.jgit.transport.URIish; | |||
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; | |||
import org.eclipse.jgit.transport.http.HttpConnectionFactory; | |||
import org.eclipse.jgit.util.HttpSupport; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
@@ -56,7 +56,7 @@ import org.junit.runner.RunWith; | |||
import org.junit.runners.Parameterized; | |||
@RunWith(Parameterized.class) | |||
public class SmartClientSmartServerSslTest extends AllFactoriesHttpTestCase { | |||
public class SmartClientSmartServerSslTest extends AllProtocolsHttpTestCase { | |||
// We run these tests with a server on localhost with a self-signed | |||
// certificate. We don't do authentication tests here, so there's no need | |||
@@ -112,8 +112,8 @@ public class SmartClientSmartServerSslTest extends AllFactoriesHttpTestCase { | |||
private RevCommit A, B; | |||
public SmartClientSmartServerSslTest(HttpConnectionFactory cf) { | |||
super(cf); | |||
public SmartClientSmartServerSslTest(TestParameters params) { | |||
super(params); | |||
} | |||
@Override | |||
@@ -128,10 +128,11 @@ public class SmartClientSmartServerSslTest extends AllFactoriesHttpTestCase { | |||
final TestRepository<Repository> src = createTestRepository(); | |||
final String srcName = src.getRepository().getDirectory().getName(); | |||
src.getRepository() | |||
.getConfig() | |||
.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, | |||
ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true); | |||
StoredConfig cfg = src.getRepository().getConfig(); | |||
cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, | |||
ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true); | |||
cfg.setInt("protocol", null, "version", enableProtocolV2 ? 2 : 0); | |||
cfg.save(); | |||
GitServlet gs = new GitServlet(); | |||
@@ -238,7 +239,7 @@ public class SmartClientSmartServerSslTest extends AllFactoriesHttpTestCase { | |||
fsck(dst, B); | |||
List<AccessEvent> requests = getRequests(); | |||
assertEquals(2, requests.size()); | |||
assertEquals(enableProtocolV2 ? 3 : 2, requests.size()); | |||
} | |||
@Test | |||
@@ -256,7 +257,7 @@ public class SmartClientSmartServerSslTest extends AllFactoriesHttpTestCase { | |||
fsck(dst, B); | |||
List<AccessEvent> requests = getRequests(); | |||
assertEquals(3, requests.size()); | |||
assertEquals(enableProtocolV2 ? 4 : 3, requests.size()); | |||
} | |||
@Test |
@@ -1,5 +1,5 @@ | |||
/* | |||
* Copyright (C) 2010, 2017 Google Inc. and others | |||
* Copyright (C) 2010, 2020 Google Inc. and others | |||
* | |||
* This program and the accompanying materials are made available under the | |||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||
@@ -22,10 +22,17 @@ import static org.junit.Assert.assertThrows; | |||
import static org.junit.Assert.assertTrue; | |||
import static org.junit.Assert.fail; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.io.OutputStreamWriter; | |||
import java.io.PrintWriter; | |||
import java.io.Writer; | |||
import java.net.Proxy; | |||
import java.net.URI; | |||
import java.net.URISyntaxException; | |||
import java.net.URL; | |||
import java.nio.charset.StandardCharsets; | |||
import java.text.MessageFormat; | |||
import java.util.Collections; | |||
import java.util.EnumSet; | |||
@@ -48,6 +55,8 @@ import javax.servlet.http.HttpServletResponse; | |||
import org.eclipse.jetty.servlet.FilterHolder; | |||
import org.eclipse.jetty.servlet.ServletContextHandler; | |||
import org.eclipse.jetty.servlet.ServletHolder; | |||
import org.eclipse.jgit.api.Git; | |||
import org.eclipse.jgit.api.TransportConfigCallback; | |||
import org.eclipse.jgit.errors.RemoteRepositoryException; | |||
import org.eclipse.jgit.errors.TransportException; | |||
import org.eclipse.jgit.errors.UnsupportedCredentialItem; | |||
@@ -70,6 +79,7 @@ import org.eclipse.jgit.lib.ReflogEntry; | |||
import org.eclipse.jgit.lib.ReflogReader; | |||
import org.eclipse.jgit.lib.Repository; | |||
import org.eclipse.jgit.lib.StoredConfig; | |||
import org.eclipse.jgit.lib.TextProgressMonitor; | |||
import org.eclipse.jgit.revwalk.RevBlob; | |||
import org.eclipse.jgit.revwalk.RevCommit; | |||
import org.eclipse.jgit.revwalk.RevWalk; | |||
@@ -86,13 +96,14 @@ import org.eclipse.jgit.transport.TransportHttp; | |||
import org.eclipse.jgit.transport.URIish; | |||
import org.eclipse.jgit.transport.UploadPack; | |||
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; | |||
import org.eclipse.jgit.transport.http.HttpConnection; | |||
import org.eclipse.jgit.transport.http.HttpConnectionFactory; | |||
import org.eclipse.jgit.util.HttpSupport; | |||
import org.eclipse.jgit.util.SystemReader; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
public class SmartClientSmartServerTest extends AllProtocolsHttpTestCase { | |||
private static final String HDR_TRANSFER_ENCODING = "Transfer-Encoding"; | |||
private AdvertiseRefsHook advertiseRefsHook; | |||
@@ -120,8 +131,8 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
private RevCommit A, B, unreachableCommit; | |||
public SmartClientSmartServerTest(HttpConnectionFactory cf) { | |||
super(cf); | |||
public SmartClientSmartServerTest(TestParameters params) { | |||
super(params); | |||
} | |||
@Override | |||
@@ -131,10 +142,11 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
final TestRepository<Repository> src = createTestRepository(); | |||
final String srcName = src.getRepository().getDirectory().getName(); | |||
src.getRepository() | |||
.getConfig() | |||
.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, | |||
ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true); | |||
StoredConfig cfg = src.getRepository().getConfig(); | |||
cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, | |||
ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true); | |||
cfg.setInt("protocol", null, "version", enableProtocolV2 ? 2 : 0); | |||
cfg.save(); | |||
GitServlet gs = new GitServlet(); | |||
gs.setUploadPackFactory((HttpServletRequest req, Repository db) -> { | |||
@@ -448,7 +460,7 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
assertEquals(B, map.get(Constants.HEAD).getObjectId()); | |||
List<AccessEvent> requests = getRequests(); | |||
assertEquals(1, requests.size()); | |||
assertEquals(enableProtocolV2 ? 2 : 1, requests.size()); | |||
AccessEvent info = requests.get(0); | |||
assertEquals("GET", info.getMethod()); | |||
@@ -458,7 +470,22 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
assertEquals(200, info.getStatus()); | |||
assertEquals("application/x-git-upload-pack-advertisement", info | |||
.getResponseHeader(HDR_CONTENT_TYPE)); | |||
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING)); | |||
if (!enableProtocolV2) { | |||
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING)); | |||
} else { | |||
AccessEvent lsRefs = requests.get(1); | |||
assertEquals("POST", lsRefs.getMethod()); | |||
assertEquals(join(remoteURI, "git-upload-pack"), lsRefs.getPath()); | |||
assertEquals(0, lsRefs.getParameters().size()); | |||
assertNotNull("has content-length", | |||
lsRefs.getRequestHeader(HDR_CONTENT_LENGTH)); | |||
assertNull("not chunked", | |||
lsRefs.getRequestHeader(HDR_TRANSFER_ENCODING)); | |||
assertEquals("version=2", lsRefs.getRequestHeader("Git-Protocol")); | |||
assertEquals(200, lsRefs.getStatus()); | |||
assertEquals("application/x-git-upload-pack-result", | |||
lsRefs.getResponseHeader(HDR_CONTENT_TYPE)); | |||
} | |||
} | |||
@Test | |||
@@ -576,9 +603,10 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
} | |||
List<AccessEvent> requests = getRequests(); | |||
assertEquals(2, requests.size()); | |||
assertEquals(enableProtocolV2 ? 3 : 2, requests.size()); | |||
AccessEvent info = requests.get(0); | |||
int requestNumber = 0; | |||
AccessEvent info = requests.get(requestNumber++); | |||
assertEquals("GET", info.getMethod()); | |||
assertEquals(join(remoteURI, "info/refs"), info.getPath()); | |||
assertEquals(1, info.getParameters().size()); | |||
@@ -586,9 +614,24 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
assertEquals(200, info.getStatus()); | |||
assertEquals("application/x-git-upload-pack-advertisement", info | |||
.getResponseHeader(HDR_CONTENT_TYPE)); | |||
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING)); | |||
if (!enableProtocolV2) { | |||
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING)); | |||
} else { | |||
AccessEvent lsRefs = requests.get(requestNumber++); | |||
assertEquals("POST", lsRefs.getMethod()); | |||
assertEquals(join(remoteURI, "git-upload-pack"), lsRefs.getPath()); | |||
assertEquals(0, lsRefs.getParameters().size()); | |||
assertNotNull("has content-length", | |||
lsRefs.getRequestHeader(HDR_CONTENT_LENGTH)); | |||
assertNull("not chunked", | |||
lsRefs.getRequestHeader(HDR_TRANSFER_ENCODING)); | |||
assertEquals("version=2", lsRefs.getRequestHeader("Git-Protocol")); | |||
assertEquals(200, lsRefs.getStatus()); | |||
assertEquals("application/x-git-upload-pack-result", | |||
lsRefs.getResponseHeader(HDR_CONTENT_TYPE)); | |||
} | |||
AccessEvent service = requests.get(1); | |||
AccessEvent service = requests.get(requestNumber); | |||
assertEquals("POST", service.getMethod()); | |||
assertEquals(join(remoteURI, "git-upload-pack"), service.getPath()); | |||
assertEquals(0, service.getParameters().size()); | |||
@@ -602,6 +645,63 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
.getResponseHeader(HDR_CONTENT_TYPE)); | |||
} | |||
@Test | |||
public void test_CloneWithCustomFactory() throws Exception { | |||
HttpConnectionFactory globalFactory = HttpTransport | |||
.getConnectionFactory(); | |||
HttpConnectionFactory failingConnectionFactory = new HttpConnectionFactory() { | |||
@Override | |||
public HttpConnection create(URL url) throws IOException { | |||
throw new IOException("Should not be reached"); | |||
} | |||
@Override | |||
public HttpConnection create(URL url, Proxy proxy) | |||
throws IOException { | |||
throw new IOException("Should not be reached"); | |||
} | |||
}; | |||
HttpTransport.setConnectionFactory(failingConnectionFactory); | |||
try { | |||
File tmp = createTempDirectory("cloneViaApi"); | |||
boolean[] localFactoryUsed = { false }; | |||
TransportConfigCallback callback = new TransportConfigCallback() { | |||
@Override | |||
public void configure(Transport transport) { | |||
if (transport instanceof TransportHttp) { | |||
((TransportHttp) transport).setHttpConnectionFactory( | |||
new HttpConnectionFactory() { | |||
@Override | |||
public HttpConnection create(URL url) | |||
throws IOException { | |||
localFactoryUsed[0] = true; | |||
return globalFactory.create(url); | |||
} | |||
@Override | |||
public HttpConnection create(URL url, | |||
Proxy proxy) throws IOException { | |||
localFactoryUsed[0] = true; | |||
return globalFactory.create(url, proxy); | |||
} | |||
}); | |||
} | |||
} | |||
}; | |||
try (Git git = Git.cloneRepository().setDirectory(tmp) | |||
.setTransportConfigCallback(callback) | |||
.setURI(remoteURI.toPrivateString()).call()) { | |||
assertTrue("Should have used the local HttpConnectionFactory", | |||
localFactoryUsed[0]); | |||
} | |||
} finally { | |||
HttpTransport.setConnectionFactory(globalFactory); | |||
} | |||
} | |||
private void initialClone_Redirect(int nofRedirects, int code) | |||
throws Exception { | |||
initialClone_Redirect(nofRedirects, code, false); | |||
@@ -628,7 +728,8 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
} | |||
List<AccessEvent> requests = getRequests(); | |||
assertEquals(2 + nofRedirects, requests.size()); | |||
assertEquals((enableProtocolV2 ? 3 : 2) + nofRedirects, | |||
requests.size()); | |||
int n = 0; | |||
while (n < nofRedirects) { | |||
@@ -644,7 +745,22 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
assertEquals(200, info.getStatus()); | |||
assertEquals("application/x-git-upload-pack-advertisement", | |||
info.getResponseHeader(HDR_CONTENT_TYPE)); | |||
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING)); | |||
if (!enableProtocolV2) { | |||
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING)); | |||
} else { | |||
AccessEvent lsRefs = requests.get(n++); | |||
assertEquals("POST", lsRefs.getMethod()); | |||
assertEquals(join(remoteURI, "git-upload-pack"), lsRefs.getPath()); | |||
assertEquals(0, lsRefs.getParameters().size()); | |||
assertNotNull("has content-length", | |||
lsRefs.getRequestHeader(HDR_CONTENT_LENGTH)); | |||
assertNull("not chunked", | |||
lsRefs.getRequestHeader(HDR_TRANSFER_ENCODING)); | |||
assertEquals("version=2", lsRefs.getRequestHeader("Git-Protocol")); | |||
assertEquals(200, lsRefs.getStatus()); | |||
assertEquals("application/x-git-upload-pack-result", | |||
lsRefs.getResponseHeader(HDR_CONTENT_TYPE)); | |||
} | |||
AccessEvent service = requests.get(n++); | |||
assertEquals("POST", service.getMethod()); | |||
@@ -756,7 +872,7 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
} | |||
List<AccessEvent> requests = getRequests(); | |||
assertEquals(3, requests.size()); | |||
assertEquals(enableProtocolV2 ? 4 : 3, requests.size()); | |||
AccessEvent info = requests.get(0); | |||
assertEquals("GET", info.getMethod()); | |||
@@ -766,24 +882,27 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
assertEquals(200, info.getStatus()); | |||
assertEquals("application/x-git-upload-pack-advertisement", | |||
info.getResponseHeader(HDR_CONTENT_TYPE)); | |||
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING)); | |||
if (!enableProtocolV2) { | |||
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING)); | |||
} | |||
AccessEvent redirect = requests.get(1); | |||
assertEquals("POST", redirect.getMethod()); | |||
assertEquals(301, redirect.getStatus()); | |||
AccessEvent service = requests.get(2); | |||
assertEquals("POST", service.getMethod()); | |||
assertEquals(join(remoteURI, "git-upload-pack"), service.getPath()); | |||
assertEquals(0, service.getParameters().size()); | |||
assertNotNull("has content-length", | |||
service.getRequestHeader(HDR_CONTENT_LENGTH)); | |||
assertNull("not chunked", | |||
service.getRequestHeader(HDR_TRANSFER_ENCODING)); | |||
assertEquals(200, service.getStatus()); | |||
assertEquals("application/x-git-upload-pack-result", | |||
service.getResponseHeader(HDR_CONTENT_TYPE)); | |||
for (int i = 2; i < requests.size(); i++) { | |||
AccessEvent service = requests.get(i); | |||
assertEquals("POST", service.getMethod()); | |||
assertEquals(join(remoteURI, "git-upload-pack"), service.getPath()); | |||
assertEquals(0, service.getParameters().size()); | |||
assertNotNull("has content-length", | |||
service.getRequestHeader(HDR_CONTENT_LENGTH)); | |||
assertNull("not chunked", | |||
service.getRequestHeader(HDR_TRANSFER_ENCODING)); | |||
assertEquals(200, service.getStatus()); | |||
assertEquals("application/x-git-upload-pack-result", | |||
service.getResponseHeader(HDR_CONTENT_TYPE)); | |||
} | |||
} | |||
@Test | |||
@@ -817,6 +936,35 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
} | |||
} | |||
private void assertFetchRequests(List<AccessEvent> requests, int index) { | |||
AccessEvent info = requests.get(index++); | |||
assertEquals("GET", info.getMethod()); | |||
assertEquals(join(authURI, "info/refs"), info.getPath()); | |||
assertEquals(1, info.getParameters().size()); | |||
assertEquals("git-upload-pack", info.getParameter("service")); | |||
assertEquals(200, info.getStatus()); | |||
assertEquals("application/x-git-upload-pack-advertisement", | |||
info.getResponseHeader(HDR_CONTENT_TYPE)); | |||
if (!enableProtocolV2) { | |||
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING)); | |||
} | |||
for (int i = index; i < requests.size(); i++) { | |||
AccessEvent service = requests.get(i); | |||
assertEquals("POST", service.getMethod()); | |||
assertEquals(join(authURI, "git-upload-pack"), service.getPath()); | |||
assertEquals(0, service.getParameters().size()); | |||
assertNotNull("has content-length", | |||
service.getRequestHeader(HDR_CONTENT_LENGTH)); | |||
assertNull("not chunked", | |||
service.getRequestHeader(HDR_TRANSFER_ENCODING)); | |||
assertEquals(200, service.getStatus()); | |||
assertEquals("application/x-git-upload-pack-result", | |||
service.getResponseHeader(HDR_CONTENT_TYPE)); | |||
} | |||
} | |||
@Test | |||
public void testInitialClone_WithAuthentication() throws Exception { | |||
try (Repository dst = createBareRepository(); | |||
@@ -830,34 +978,167 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
} | |||
List<AccessEvent> requests = getRequests(); | |||
assertEquals(3, requests.size()); | |||
assertEquals(enableProtocolV2 ? 4 : 3, requests.size()); | |||
AccessEvent info = requests.get(0); | |||
assertEquals("GET", info.getMethod()); | |||
assertEquals(401, info.getStatus()); | |||
info = requests.get(1); | |||
assertFetchRequests(requests, 1); | |||
} | |||
@Test | |||
public void testInitialClone_WithPreAuthentication() throws Exception { | |||
try (Repository dst = createBareRepository(); | |||
Transport t = Transport.open(dst, authURI)) { | |||
assertFalse(dst.getObjectDatabase().has(A_txt)); | |||
((TransportHttp) t).setPreemptiveBasicAuthentication( | |||
AppServer.username, AppServer.password); | |||
t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); | |||
assertTrue(dst.getObjectDatabase().has(A_txt)); | |||
assertEquals(B, dst.exactRef(master).getObjectId()); | |||
fsck(dst, B); | |||
} | |||
List<AccessEvent> requests = getRequests(); | |||
assertEquals(enableProtocolV2 ? 3 : 2, requests.size()); | |||
assertFetchRequests(requests, 0); | |||
} | |||
@Test | |||
public void testInitialClone_WithPreAuthenticationCleared() | |||
throws Exception { | |||
try (Repository dst = createBareRepository(); | |||
Transport t = Transport.open(dst, authURI)) { | |||
assertFalse(dst.getObjectDatabase().has(A_txt)); | |||
((TransportHttp) t).setPreemptiveBasicAuthentication( | |||
AppServer.username, AppServer.password); | |||
((TransportHttp) t).setPreemptiveBasicAuthentication(null, null); | |||
t.setCredentialsProvider(testCredentials); | |||
t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); | |||
assertTrue(dst.getObjectDatabase().has(A_txt)); | |||
assertEquals(B, dst.exactRef(master).getObjectId()); | |||
fsck(dst, B); | |||
} | |||
List<AccessEvent> requests = getRequests(); | |||
assertEquals(enableProtocolV2 ? 4 : 3, requests.size()); | |||
AccessEvent info = requests.get(0); | |||
assertEquals("GET", info.getMethod()); | |||
assertEquals(join(authURI, "info/refs"), info.getPath()); | |||
assertEquals(1, info.getParameters().size()); | |||
assertEquals("git-upload-pack", info.getParameter("service")); | |||
assertEquals(200, info.getStatus()); | |||
assertEquals("application/x-git-upload-pack-advertisement", | |||
info.getResponseHeader(HDR_CONTENT_TYPE)); | |||
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING)); | |||
assertEquals(401, info.getStatus()); | |||
AccessEvent service = requests.get(2); | |||
assertEquals("POST", service.getMethod()); | |||
assertEquals(join(authURI, "git-upload-pack"), service.getPath()); | |||
assertEquals(0, service.getParameters().size()); | |||
assertNotNull("has content-length", | |||
service.getRequestHeader(HDR_CONTENT_LENGTH)); | |||
assertNull("not chunked", | |||
service.getRequestHeader(HDR_TRANSFER_ENCODING)); | |||
assertFetchRequests(requests, 1); | |||
} | |||
assertEquals(200, service.getStatus()); | |||
assertEquals("application/x-git-upload-pack-result", | |||
service.getResponseHeader(HDR_CONTENT_TYPE)); | |||
@Test | |||
public void testInitialClone_PreAuthenticationTooLate() throws Exception { | |||
try (Repository dst = createBareRepository(); | |||
Transport t = Transport.open(dst, authURI)) { | |||
assertFalse(dst.getObjectDatabase().has(A_txt)); | |||
((TransportHttp) t).setPreemptiveBasicAuthentication( | |||
AppServer.username, AppServer.password); | |||
t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); | |||
assertTrue(dst.getObjectDatabase().has(A_txt)); | |||
assertEquals(B, dst.exactRef(master).getObjectId()); | |||
fsck(dst, B); | |||
List<AccessEvent> requests = getRequests(); | |||
assertEquals(enableProtocolV2 ? 3 : 2, requests.size()); | |||
assertFetchRequests(requests, 0); | |||
assertThrows(IllegalStateException.class, | |||
() -> ((TransportHttp) t).setPreemptiveBasicAuthentication( | |||
AppServer.username, AppServer.password)); | |||
assertThrows(IllegalStateException.class, () -> ((TransportHttp) t) | |||
.setPreemptiveBasicAuthentication(null, null)); | |||
} | |||
} | |||
@Test | |||
public void testInitialClone_WithWrongPreAuthenticationAndCredentialProvider() | |||
throws Exception { | |||
try (Repository dst = createBareRepository(); | |||
Transport t = Transport.open(dst, authURI)) { | |||
assertFalse(dst.getObjectDatabase().has(A_txt)); | |||
((TransportHttp) t).setPreemptiveBasicAuthentication( | |||
AppServer.username, AppServer.password + 'x'); | |||
t.setCredentialsProvider(testCredentials); | |||
t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); | |||
assertTrue(dst.getObjectDatabase().has(A_txt)); | |||
assertEquals(B, dst.exactRef(master).getObjectId()); | |||
fsck(dst, B); | |||
} | |||
List<AccessEvent> requests = getRequests(); | |||
assertEquals(enableProtocolV2 ? 4 : 3, requests.size()); | |||
AccessEvent info = requests.get(0); | |||
assertEquals("GET", info.getMethod()); | |||
assertEquals(401, info.getStatus()); | |||
assertFetchRequests(requests, 1); | |||
} | |||
@Test | |||
public void testInitialClone_WithWrongPreAuthentication() throws Exception { | |||
try (Repository dst = createBareRepository(); | |||
Transport t = Transport.open(dst, authURI)) { | |||
assertFalse(dst.getObjectDatabase().has(A_txt)); | |||
((TransportHttp) t).setPreemptiveBasicAuthentication( | |||
AppServer.username, AppServer.password + 'x'); | |||
TransportException e = assertThrows(TransportException.class, | |||
() -> t.fetch(NullProgressMonitor.INSTANCE, | |||
mirror(master))); | |||
String msg = e.getMessage(); | |||
assertTrue("Unexpected exception message: " + msg, | |||
msg.contains("no CredentialsProvider")); | |||
} | |||
List<AccessEvent> requests = getRequests(); | |||
assertEquals(1, requests.size()); | |||
AccessEvent info = requests.get(0); | |||
assertEquals("GET", info.getMethod()); | |||
assertEquals(401, info.getStatus()); | |||
} | |||
@Test | |||
public void testInitialClone_WithUserInfo() throws Exception { | |||
URIish withUserInfo = authURI.setUser(AppServer.username) | |||
.setPass(AppServer.password); | |||
try (Repository dst = createBareRepository(); | |||
Transport t = Transport.open(dst, withUserInfo)) { | |||
assertFalse(dst.getObjectDatabase().has(A_txt)); | |||
t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); | |||
assertTrue(dst.getObjectDatabase().has(A_txt)); | |||
assertEquals(B, dst.exactRef(master).getObjectId()); | |||
fsck(dst, B); | |||
} | |||
List<AccessEvent> requests = getRequests(); | |||
assertEquals(enableProtocolV2 ? 3 : 2, requests.size()); | |||
assertFetchRequests(requests, 0); | |||
} | |||
@Test | |||
public void testInitialClone_PreAuthOverridesUserInfo() throws Exception { | |||
URIish withUserInfo = authURI.setUser(AppServer.username) | |||
.setPass(AppServer.password + 'x'); | |||
try (Repository dst = createBareRepository(); | |||
Transport t = Transport.open(dst, withUserInfo)) { | |||
assertFalse(dst.getObjectDatabase().has(A_txt)); | |||
((TransportHttp) t).setPreemptiveBasicAuthentication( | |||
AppServer.username, AppServer.password); | |||
t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); | |||
assertTrue(dst.getObjectDatabase().has(A_txt)); | |||
assertEquals(B, dst.exactRef(master).getObjectId()); | |||
fsck(dst, B); | |||
} | |||
List<AccessEvent> requests = getRequests(); | |||
assertEquals(enableProtocolV2 ? 3 : 2, requests.size()); | |||
assertFetchRequests(requests, 0); | |||
} | |||
@Test | |||
@@ -937,19 +1218,20 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
} | |||
List<AccessEvent> requests = getRequests(); | |||
assertEquals(4, requests.size()); | |||
assertEquals(enableProtocolV2 ? 5 : 4, requests.size()); | |||
AccessEvent redirect = requests.get(0); | |||
int requestNumber = 0; | |||
AccessEvent redirect = requests.get(requestNumber++); | |||
assertEquals("GET", redirect.getMethod()); | |||
assertEquals(join(cloneFrom, "info/refs"), redirect.getPath()); | |||
assertEquals(301, redirect.getStatus()); | |||
AccessEvent info = requests.get(1); | |||
AccessEvent info = requests.get(requestNumber++); | |||
assertEquals("GET", info.getMethod()); | |||
assertEquals(join(authURI, "info/refs"), info.getPath()); | |||
assertEquals(401, info.getStatus()); | |||
info = requests.get(2); | |||
info = requests.get(requestNumber++); | |||
assertEquals("GET", info.getMethod()); | |||
assertEquals(join(authURI, "info/refs"), info.getPath()); | |||
assertEquals(1, info.getParameters().size()); | |||
@@ -957,9 +1239,24 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
assertEquals(200, info.getStatus()); | |||
assertEquals("application/x-git-upload-pack-advertisement", | |||
info.getResponseHeader(HDR_CONTENT_TYPE)); | |||
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING)); | |||
if (!enableProtocolV2) { | |||
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING)); | |||
} else { | |||
AccessEvent lsRefs = requests.get(requestNumber++); | |||
assertEquals("POST", lsRefs.getMethod()); | |||
assertEquals(join(authURI, "git-upload-pack"), lsRefs.getPath()); | |||
assertEquals(0, lsRefs.getParameters().size()); | |||
assertNotNull("has content-length", | |||
lsRefs.getRequestHeader(HDR_CONTENT_LENGTH)); | |||
assertNull("not chunked", | |||
lsRefs.getRequestHeader(HDR_TRANSFER_ENCODING)); | |||
assertEquals("version=2", lsRefs.getRequestHeader("Git-Protocol")); | |||
assertEquals(200, lsRefs.getStatus()); | |||
assertEquals("application/x-git-upload-pack-result", | |||
lsRefs.getResponseHeader(HDR_CONTENT_TYPE)); | |||
} | |||
AccessEvent service = requests.get(3); | |||
AccessEvent service = requests.get(requestNumber); | |||
assertEquals("POST", service.getMethod()); | |||
assertEquals(join(authURI, "git-upload-pack"), service.getPath()); | |||
assertEquals(0, service.getParameters().size()); | |||
@@ -987,7 +1284,7 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
} | |||
List<AccessEvent> requests = getRequests(); | |||
assertEquals(3, requests.size()); | |||
assertEquals(enableProtocolV2 ? 4 : 3, requests.size()); | |||
AccessEvent info = requests.get(0); | |||
assertEquals("GET", info.getMethod()); | |||
@@ -997,25 +1294,30 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
assertEquals(200, info.getStatus()); | |||
assertEquals("application/x-git-upload-pack-advertisement", | |||
info.getResponseHeader(HDR_CONTENT_TYPE)); | |||
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING)); | |||
if (!enableProtocolV2) { | |||
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING)); | |||
} | |||
AccessEvent service = requests.get(1); | |||
assertEquals("POST", service.getMethod()); | |||
assertEquals(join(authOnPostURI, "git-upload-pack"), service.getPath()); | |||
assertEquals(401, service.getStatus()); | |||
service = requests.get(2); | |||
assertEquals("POST", service.getMethod()); | |||
assertEquals(join(authOnPostURI, "git-upload-pack"), service.getPath()); | |||
assertEquals(0, service.getParameters().size()); | |||
assertNotNull("has content-length", | |||
service.getRequestHeader(HDR_CONTENT_LENGTH)); | |||
assertNull("not chunked", | |||
service.getRequestHeader(HDR_TRANSFER_ENCODING)); | |||
assertEquals(200, service.getStatus()); | |||
assertEquals("application/x-git-upload-pack-result", | |||
service.getResponseHeader(HDR_CONTENT_TYPE)); | |||
for (int i = 2; i < requests.size(); i++) { | |||
service = requests.get(i); | |||
assertEquals("POST", service.getMethod()); | |||
assertEquals(join(authOnPostURI, "git-upload-pack"), | |||
service.getPath()); | |||
assertEquals(0, service.getParameters().size()); | |||
assertNotNull("has content-length", | |||
service.getRequestHeader(HDR_CONTENT_LENGTH)); | |||
assertNull("not chunked", | |||
service.getRequestHeader(HDR_TRANSFER_ENCODING)); | |||
assertEquals(200, service.getStatus()); | |||
assertEquals("application/x-git-upload-pack-result", | |||
service.getResponseHeader(HDR_CONTENT_TYPE)); | |||
} | |||
} | |||
@Test | |||
@@ -1052,9 +1354,11 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
List<AccessEvent> requests = getRequests(); | |||
requests.removeAll(cloneRequests); | |||
assertEquals(2, requests.size()); | |||
AccessEvent info = requests.get(0); | |||
assertEquals(enableProtocolV2 ? 3 : 2, requests.size()); | |||
int requestNumber = 0; | |||
AccessEvent info = requests.get(requestNumber++); | |||
assertEquals("GET", info.getMethod()); | |||
assertEquals(join(remoteURI, "info/refs"), info.getPath()); | |||
assertEquals(1, info.getParameters().size()); | |||
@@ -1063,9 +1367,24 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
assertEquals("application/x-git-upload-pack-advertisement", | |||
info.getResponseHeader(HDR_CONTENT_TYPE)); | |||
if (enableProtocolV2) { | |||
AccessEvent lsRefs = requests.get(requestNumber++); | |||
assertEquals("POST", lsRefs.getMethod()); | |||
assertEquals(join(remoteURI, "git-upload-pack"), lsRefs.getPath()); | |||
assertEquals(0, lsRefs.getParameters().size()); | |||
assertNotNull("has content-length", | |||
lsRefs.getRequestHeader(HDR_CONTENT_LENGTH)); | |||
assertNull("not chunked", | |||
lsRefs.getRequestHeader(HDR_TRANSFER_ENCODING)); | |||
assertEquals("version=2", lsRefs.getRequestHeader("Git-Protocol")); | |||
assertEquals(200, lsRefs.getStatus()); | |||
assertEquals("application/x-git-upload-pack-result", | |||
lsRefs.getResponseHeader(HDR_CONTENT_TYPE)); | |||
} | |||
// We should have needed one request to perform the fetch. | |||
// | |||
AccessEvent service = requests.get(1); | |||
AccessEvent service = requests.get(requestNumber); | |||
assertEquals("POST", service.getMethod()); | |||
assertEquals(join(remoteURI, "git-upload-pack"), service.getPath()); | |||
assertEquals(0, service.getParameters().size()); | |||
@@ -1116,9 +1435,10 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
List<AccessEvent> requests = getRequests(); | |||
requests.removeAll(cloneRequests); | |||
assertEquals(3, requests.size()); | |||
assertEquals(enableProtocolV2 ? 4 : 3, requests.size()); | |||
AccessEvent info = requests.get(0); | |||
int requestNumber = 0; | |||
AccessEvent info = requests.get(requestNumber++); | |||
assertEquals("GET", info.getMethod()); | |||
assertEquals(join(remoteURI, "info/refs"), info.getPath()); | |||
assertEquals(1, info.getParameters().size()); | |||
@@ -1127,10 +1447,25 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
assertEquals("application/x-git-upload-pack-advertisement", info | |||
.getResponseHeader(HDR_CONTENT_TYPE)); | |||
if (enableProtocolV2) { | |||
AccessEvent lsRefs = requests.get(requestNumber++); | |||
assertEquals("POST", lsRefs.getMethod()); | |||
assertEquals(join(remoteURI, "git-upload-pack"), lsRefs.getPath()); | |||
assertEquals(0, lsRefs.getParameters().size()); | |||
assertNotNull("has content-length", | |||
lsRefs.getRequestHeader(HDR_CONTENT_LENGTH)); | |||
assertNull("not chunked", | |||
lsRefs.getRequestHeader(HDR_TRANSFER_ENCODING)); | |||
assertEquals("version=2", lsRefs.getRequestHeader("Git-Protocol")); | |||
assertEquals(200, lsRefs.getStatus()); | |||
assertEquals("application/x-git-upload-pack-result", | |||
lsRefs.getResponseHeader(HDR_CONTENT_TYPE)); | |||
} | |||
// We should have needed two requests to perform the fetch | |||
// due to the high number of local unknown commits. | |||
// | |||
AccessEvent service = requests.get(1); | |||
AccessEvent service = requests.get(requestNumber++); | |||
assertEquals("POST", service.getMethod()); | |||
assertEquals(join(remoteURI, "git-upload-pack"), service.getPath()); | |||
assertEquals(0, service.getParameters().size()); | |||
@@ -1143,7 +1478,7 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
assertEquals("application/x-git-upload-pack-result", service | |||
.getResponseHeader(HDR_CONTENT_TYPE)); | |||
service = requests.get(2); | |||
service = requests.get(requestNumber); | |||
assertEquals("POST", service.getMethod()); | |||
assertEquals(join(remoteURI, "git-upload-pack"), service.getPath()); | |||
assertEquals(0, service.getParameters().size()); | |||
@@ -1157,6 +1492,64 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
.getResponseHeader(HDR_CONTENT_TYPE)); | |||
} | |||
@Test | |||
public void testFetch_MaxHavesCutoffAfterAckOnly() throws Exception { | |||
// Bootstrap by doing the clone. | |||
// | |||
TestRepository dst = createTestRepository(); | |||
try (Transport t = Transport.open(dst.getRepository(), remoteURI)) { | |||
t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); | |||
} | |||
assertEquals(B, dst.getRepository().exactRef(master).getObjectId()); | |||
// Force enough into the local client that enumeration will | |||
// need more than MAX_HAVES (256) haves to be sent. The server | |||
// doesn't know any of these, so it will never ACK. The client | |||
// should keep going. | |||
// | |||
// If it does, client and server will find a common commit, and | |||
// the pack file will contain exactly the one commit object Z | |||
// we create on the remote, which we can test for via the progress | |||
// monitor, which should have something like | |||
// "Receiving objects: 100% (1/1)". If the client sends a "done" | |||
// too early, the server will send more objects, and we'll have | |||
// a line like "Receiving objects: 100% (8/8)". | |||
TestRepository.BranchBuilder b = dst.branch(master); | |||
// The client will send 32 + 64 + 128 + 256 + 512 haves. Only the | |||
// last one will be a common commit. If the cutoff kicks in too | |||
// early (after 480), we'll get too many objects in the fetch. | |||
for (int i = 0; i < 992; i++) | |||
b.commit().tick(3600 /* 1 hour */).message("c" + i).create(); | |||
// Create a new commit on the remote. | |||
// | |||
RevCommit Z; | |||
try (TestRepository<Repository> tr = new TestRepository<>( | |||
remoteRepository)) { | |||
b = tr.branch(master); | |||
Z = b.commit().message("Z").create(); | |||
} | |||
// Now incrementally update. | |||
// | |||
ByteArrayOutputStream buffer = new ByteArrayOutputStream(); | |||
Writer writer = new OutputStreamWriter(buffer, StandardCharsets.UTF_8); | |||
TextProgressMonitor monitor = new TextProgressMonitor(writer); | |||
try (Transport t = Transport.open(dst.getRepository(), remoteURI)) { | |||
t.fetch(monitor, mirror(master)); | |||
} | |||
assertEquals(Z, dst.getRepository().exactRef(master).getObjectId()); | |||
String progressMessages = new String(buffer.toByteArray(), | |||
StandardCharsets.UTF_8); | |||
Pattern expected = Pattern | |||
.compile("Receiving objects:\\s+100% \\(1/1\\)\n"); | |||
if (!expected.matcher(progressMessages).find()) { | |||
System.out.println(progressMessages); | |||
fail("Expected only one object to be sent"); | |||
} | |||
} | |||
@Test | |||
public void testInitialClone_BrokenServer() throws Exception { | |||
try (Repository dst = createBareRepository(); | |||
@@ -1211,7 +1604,8 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
Collections.<ObjectId> emptySet()); | |||
fail("Server accepted want " + id.name()); | |||
} catch (TransportException err) { | |||
assertEquals("want " + id.name() + " not valid", err.getMessage()); | |||
assertTrue(err.getMessage() | |||
.contains("want " + id.name() + " not valid")); | |||
} | |||
} | |||
@@ -1224,6 +1618,8 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
new DfsRepositoryDescription(repoName)); | |||
final TestRepository<Repository> repo = new TestRepository<>( | |||
badRefsRepo); | |||
badRefsRepo.getConfig().setInt("protocol", null, "version", | |||
enableProtocolV2 ? 2 : 0); | |||
ServletContextHandler app = noRefServer.addContext("/git"); | |||
GitServlet gs = new GitServlet(); | |||
@@ -1253,7 +1649,8 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
Collections.<ObjectId> emptySet()); | |||
fail("Successfully served ref with value " + c.getRef(master)); | |||
} catch (TransportException err) { | |||
assertEquals("Internal server error", err.getMessage()); | |||
assertTrue("Unexpected exception message " + err.getMessage(), | |||
err.getMessage().contains("Internal server error")); | |||
} | |||
} finally { | |||
noRefServer.tearDown(); | |||
@@ -1429,5 +1826,4 @@ public class SmartClientSmartServerTest extends AllFactoriesHttpTestCase { | |||
cfg.setBoolean("http", null, "receivepack", true); | |||
cfg.save(); | |||
} | |||
} |
@@ -23,8 +23,8 @@ import java.nio.file.Files; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import java.util.Locale; | |||
import java.util.Map; | |||
import java.util.concurrent.ConcurrentHashMap; | |||
import java.util.concurrent.ConcurrentMap; | |||
import org.eclipse.jetty.http.HttpVersion; | |||
import org.eclipse.jetty.security.AbstractLoginService; | |||
@@ -259,7 +259,7 @@ public class AppServer { | |||
static class TestMappedLoginService extends AbstractLoginService { | |||
private String role; | |||
protected final ConcurrentMap<String, UserPrincipal> users = new ConcurrentHashMap<>(); | |||
protected final Map<String, UserPrincipal> users = new ConcurrentHashMap<>(); | |||
TestMappedLoginService(String role) { | |||
this.role = role; |
@@ -0,0 +1,145 @@ | |||
#SpotBugs User Preferences | |||
#Fri Dec 04 11:26:04 CET 2020 | |||
detectorExplicitSerialization=ExplicitSerialization|true | |||
detectorMultithreadedInstanceAccess=MultithreadedInstanceAccess|true | |||
detectorConfusionBetweenInheritedAndOuterMethod=ConfusionBetweenInheritedAndOuterMethod|true | |||
detectorWrongMapIterator=WrongMapIterator|true | |||
detectorUnnecessaryMath=UnnecessaryMath|true | |||
detectorUselessSubclassMethod=UselessSubclassMethod|false | |||
filter_settings=Medium|BAD_PRACTICE,CORRECTNESS,EXPERIMENTAL,I18N,MALICIOUS_CODE,MT_CORRECTNESS,PERFORMANCE,SECURITY,STYLE|false|15 | |||
detectorURLProblems=URLProblems|true | |||
detectorIteratorIdioms=IteratorIdioms|true | |||
detectorMutableEnum=MutableEnum|true | |||
detectorFindNonShortCircuit=FindNonShortCircuit|true | |||
detectorSynchronizeAndNullCheckField=SynchronizeAndNullCheckField|true | |||
detectorVolatileUsage=VolatileUsage|true | |||
detectorFindNakedNotify=FindNakedNotify|true | |||
detectorFindUninitializedGet=FindUninitializedGet|true | |||
detectorFindUseOfNonSerializableValue=FindUseOfNonSerializableValue|true | |||
detectorFindJSR166LockMonitorenter=FindJSR166LockMonitorenter|true | |||
detectorQuestionableBooleanAssignment=QuestionableBooleanAssignment|true | |||
detectorSwitchFallthrough=SwitchFallthrough|true | |||
detectorFindLocalSelfAssignment2=FindLocalSelfAssignment2|true | |||
detectorConfusedInheritance=ConfusedInheritance|true | |||
detectorSynchronizationOnSharedBuiltinConstant=SynchronizationOnSharedBuiltinConstant|true | |||
detectorMutableStaticFields=MutableStaticFields|true | |||
detectorInvalidJUnitTest=InvalidJUnitTest|true | |||
detectorInfiniteLoop=InfiniteLoop|true | |||
detectorFindRunInvocations=FindRunInvocations|true | |||
detectorBadSyntaxForRegularExpression=BadSyntaxForRegularExpression|true | |||
detectorXMLFactoryBypass=XMLFactoryBypass|true | |||
detectorFindOpenStream=FindOpenStream|true | |||
detectorCheckExpectedWarnings=CheckExpectedWarnings|false | |||
detectorHugeSharedStringConstants=HugeSharedStringConstants|true | |||
detectorLostLoggerDueToWeakReference=LostLoggerDueToWeakReference|true | |||
detectorStringConcatenation=StringConcatenation|true | |||
detectorLoadOfKnownNullValue=LoadOfKnownNullValue|true | |||
detectorFinalizerNullsFields=FinalizerNullsFields|true | |||
detectorFindFieldSelfAssignment=FindFieldSelfAssignment|true | |||
detectorInefficientToArray=InefficientToArray|false | |||
detectorDontCatchIllegalMonitorStateException=DontCatchIllegalMonitorStateException|true | |||
detectorInconsistentAnnotations=InconsistentAnnotations|true | |||
detectorBadlyOverriddenAdapter=BadlyOverriddenAdapter|true | |||
detectorInstantiateStaticClass=InstantiateStaticClass|true | |||
detectorCheckRelaxingNullnessAnnotation=CheckRelaxingNullnessAnnotation|true | |||
detectorMethodReturnCheck=MethodReturnCheck|true | |||
detectorEqualsOperandShouldHaveClassCompatibleWithThis=EqualsOperandShouldHaveClassCompatibleWithThis|true | |||
detectorFindDoubleCheck=FindDoubleCheck|true | |||
detectorFindBadForLoop=FindBadForLoop|true | |||
detectorDefaultEncodingDetector=DefaultEncodingDetector|true | |||
detectorFindInconsistentSync2=FindInconsistentSync2|true | |||
detectorFindSpinLoop=FindSpinLoop|true | |||
detectorFindMaskedFields=FindMaskedFields|true | |||
detectorBooleanReturnNull=BooleanReturnNull|true | |||
detectorFindUnsyncGet=FindUnsyncGet|true | |||
detectorCrossSiteScripting=CrossSiteScripting|true | |||
detectorDroppedException=DroppedException|true | |||
detectorFindDeadLocalStores=FindDeadLocalStores|true | |||
detectorCheckImmutableAnnotation=CheckImmutableAnnotation|true | |||
detectorInfiniteRecursiveLoop=InfiniteRecursiveLoop|true | |||
detectorFindRefComparison=FindRefComparison|true | |||
detectorFindRoughConstants=FindRoughConstants|true | |||
detectorMutableLock=MutableLock|true | |||
detectorFindNullDeref=FindNullDeref|true | |||
detectorFindReturnRef=FindReturnRef|true | |||
detectorSynchronizeOnClassLiteralNotGetClass=SynchronizeOnClassLiteralNotGetClass|true | |||
detectorFindUselessControlFlow=FindUselessControlFlow|true | |||
detectorOverridingEqualsNotSymmetrical=OverridingEqualsNotSymmetrical|true | |||
detectorIDivResultCastToDouble=IDivResultCastToDouble|true | |||
detectorReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass=ReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass|true | |||
detectorFindSelfComparison=FindSelfComparison|true | |||
detectorFindFloatEquality=FindFloatEquality|true | |||
detectorFindComparatorProblems=FindComparatorProblems|true | |||
detectorRepeatedConditionals=RepeatedConditionals|true | |||
filter_settings_neg=NOISE| | |||
detectorInefficientMemberAccess=InefficientMemberAccess|false | |||
detectorFindUncalledPrivateMethods=FindUncalledPrivateMethods|true | |||
detectorNumberConstructor=NumberConstructor|true | |||
detectorDontAssertInstanceofInTests=DontAssertInstanceofInTests|true | |||
detectorFindFinalizeInvocations=FindFinalizeInvocations|true | |||
detectorFindNullDerefsInvolvingNonShortCircuitEvaluation=FindNullDerefsInvolvingNonShortCircuitEvaluation|true | |||
detectorDontIgnoreResultOfPutIfAbsent=DontIgnoreResultOfPutIfAbsent|true | |||
detectorFindUnconditionalWait=FindUnconditionalWait|true | |||
detectorFindTwoLockWait=FindTwoLockWait|true | |||
detectorFindSleepWithLockHeld=FindSleepWithLockHeld|true | |||
detectorFindUnreleasedLock=FindUnreleasedLock|true | |||
detectorInefficientIndexOf=InefficientIndexOf|false | |||
detectorDoInsideDoPrivileged=DoInsideDoPrivileged|true | |||
detectorFindEmptySynchronizedBlock=FindEmptySynchronizedBlock|true | |||
detectorOverridingMethodsMustInvokeSuperDetector=OverridingMethodsMustInvokeSuperDetector|true | |||
detectorWaitInLoop=WaitInLoop|true | |||
detectorIntCast2LongAsInstant=IntCast2LongAsInstant|true | |||
detectorBadUseOfReturnValue=BadUseOfReturnValue|true | |||
detectorFindSqlInjection=FindSqlInjection|true | |||
detectorUnreadFields=UnreadFields|true | |||
detectorSynchronizingOnContentsOfFieldToProtectField=SynchronizingOnContentsOfFieldToProtectField|true | |||
detectorFindUselessObjects=FindUselessObjects|true | |||
detectorBadAppletConstructor=BadAppletConstructor|false | |||
detectorInheritanceUnsafeGetResource=InheritanceUnsafeGetResource|true | |||
detectorSerializableIdiom=SerializableIdiom|true | |||
detectorNaming=Naming|true | |||
detectorNoteUnconditionalParamDerefs=NoteUnconditionalParamDerefs|true | |||
detectorFormatStringChecker=FormatStringChecker|true | |||
detectorSuspiciousThreadInterrupted=SuspiciousThreadInterrupted|true | |||
detectorEmptyZipFileEntry=EmptyZipFileEntry|false | |||
detectorFindCircularDependencies=FindCircularDependencies|false | |||
detectorPreferZeroLengthArrays=PreferZeroLengthArrays|true | |||
detectorAtomicityProblem=AtomicityProblem|true | |||
detectorRuntimeExceptionCapture=RuntimeExceptionCapture|true | |||
detectorInitializationChain=InitializationChain|true | |||
detectorInitializeNonnullFieldsInConstructor=InitializeNonnullFieldsInConstructor|true | |||
detectorOptionalReturnNull=OptionalReturnNull|true | |||
detectorStartInConstructor=StartInConstructor|true | |||
detectorFindUnsatisfiedObligation=FindUnsatisfiedObligation|true | |||
detectorRedundantConditions=RedundantConditions|true | |||
effort=default | |||
detectorRedundantInterfaces=RedundantInterfaces|true | |||
detectorDuplicateBranches=DuplicateBranches|true | |||
detectorCheckTypeQualifiers=CheckTypeQualifiers|true | |||
detectorComparatorIdiom=ComparatorIdiom|true | |||
detectorFindBadCast2=FindBadCast2|true | |||
detectorFindMismatchedWaitOrNotify=FindMismatchedWaitOrNotify|true | |||
excludefilter0=findBugs/FindBugsExcludeFilter.xml|true | |||
detectorBadResultSetAccess=BadResultSetAccess|true | |||
detectorIncompatMask=IncompatMask|true | |||
detectorCovariantArrayAssignment=CovariantArrayAssignment|false | |||
detectorDumbMethodInvocations=DumbMethodInvocations|true | |||
run_at_full_build=false | |||
detectorStaticCalendarDetector=StaticCalendarDetector|true | |||
detectorUncallableMethodOfAnonymousClass=UncallableMethodOfAnonymousClass|true | |||
detectorVarArgsProblems=VarArgsProblems|true | |||
detectorInefficientInitializationInsideLoop=InefficientInitializationInsideLoop|false | |||
detectorCloneIdiom=CloneIdiom|true | |||
detectorFindHEmismatch=FindHEmismatch|true | |||
detectorAppendingToAnObjectOutputStream=AppendingToAnObjectOutputStream|true | |||
detectorFindSelfComparison2=FindSelfComparison2|true | |||
detectorLazyInit=LazyInit|true | |||
detectorFindUnrelatedTypesInGenericContainer=FindUnrelatedTypesInGenericContainer|true | |||
detectorDontUseEnum=DontUseEnum|true | |||
detectorFindPuzzlers=FindPuzzlers|true | |||
detectorCallToUnsupportedMethod=CallToUnsupportedMethod|false | |||
detectorSuperfluousInstanceOf=SuperfluousInstanceOf|true | |||
detectorReadReturnShouldBeChecked=ReadReturnShouldBeChecked|true | |||
detector_threshold=2 | |||
detectorPublicSemaphores=PublicSemaphores|false | |||
detectorDumbMethods=DumbMethods|true |
@@ -8,28 +8,31 @@ Bundle-Localization: plugin | |||
Bundle-Vendor: %Bundle-Vendor | |||
Bundle-ActivationPolicy: lazy | |||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8 | |||
Import-Package: org.apache.sshd.common;version="[2.4.0,2.5.0)", | |||
org.apache.sshd.common.config.keys;version="[2.4.0,2.5.0)", | |||
org.apache.sshd.common.file.virtualfs;version="[2.4.0,2.5.0)", | |||
org.apache.sshd.common.helpers;version="[2.4.0,2.5.0)", | |||
org.apache.sshd.common.io;version="[2.4.0,2.5.0)", | |||
org.apache.sshd.common.kex;version="[2.4.0,2.5.0)", | |||
org.apache.sshd.common.keyprovider;version="[2.4.0,2.5.0)", | |||
org.apache.sshd.common.session;version="[2.4.0,2.5.0)", | |||
org.apache.sshd.common.util.buffer;version="[2.4.0,2.5.0)", | |||
org.apache.sshd.common.util.logging;version="[2.4.0,2.5.0)", | |||
org.apache.sshd.common.util.security;version="[2.4.0,2.5.0)", | |||
org.apache.sshd.common.util.threads;version="[2.4.0,2.5.0)", | |||
org.apache.sshd.server;version="[2.4.0,2.5.0)", | |||
org.apache.sshd.server.auth;version="[2.4.0,2.5.0)", | |||
org.apache.sshd.server.auth.gss;version="[2.4.0,2.5.0)", | |||
org.apache.sshd.server.auth.keyboard;version="[2.4.0,2.5.0)", | |||
org.apache.sshd.server.auth.password;version="[2.4.0,2.5.0)", | |||
org.apache.sshd.server.command;version="[2.4.0,2.5.0)", | |||
org.apache.sshd.server.session;version="[2.4.0,2.5.0)", | |||
org.apache.sshd.server.shell;version="[2.4.0,2.5.0)", | |||
org.apache.sshd.server.subsystem;version="[2.4.0,2.5.0)", | |||
org.apache.sshd.server.subsystem.sftp;version="[2.4.0,2.5.0)", | |||
Import-Package: org.apache.sshd.common;version="[2.6.0,2.7.0)", | |||
org.apache.sshd.common.config.keys;version="[2.6.0,2.7.0)", | |||
org.apache.sshd.common.file.virtualfs;version="[2.6.0,2.7.0)", | |||
org.apache.sshd.common.helpers;version="[2.6.0,2.7.0)", | |||
org.apache.sshd.common.io;version="[2.6.0,2.7.0)", | |||
org.apache.sshd.common.kex;version="[2.6.0,2.7.0)", | |||
org.apache.sshd.common.keyprovider;version="[2.6.0,2.7.0)", | |||
org.apache.sshd.common.session;version="[2.6.0,2.7.0)", | |||
org.apache.sshd.common.signature;version="[2.6.0,2.7.0)", | |||
org.apache.sshd.common.util.buffer;version="[2.6.0,2.7.0)", | |||
org.apache.sshd.common.util.logging;version="[2.6.0,2.7.0)", | |||
org.apache.sshd.common.util.security;version="[2.6.0,2.7.0)", | |||
org.apache.sshd.common.util.threads;version="[2.6.0,2.7.0)", | |||
org.apache.sshd.core;version="[2.6.0,2.7.0)", | |||
org.apache.sshd.server;version="[2.6.0,2.7.0)", | |||
org.apache.sshd.server.auth;version="[2.6.0,2.7.0)", | |||
org.apache.sshd.server.auth.gss;version="[2.6.0,2.7.0)", | |||
org.apache.sshd.server.auth.keyboard;version="[2.6.0,2.7.0)", | |||
org.apache.sshd.server.auth.password;version="[2.6.0,2.7.0)", | |||
org.apache.sshd.server.command;version="[2.6.0,2.7.0)", | |||
org.apache.sshd.server.session;version="[2.6.0,2.7.0)", | |||
org.apache.sshd.server.shell;version="[2.6.0,2.7.0)", | |||
org.apache.sshd.server.subsystem;version="[2.6.0,2.7.0)", | |||
org.apache.sshd.sftp;version="[2.6.0,2.7.0)", | |||
org.apache.sshd.sftp.server;version="[2.6.0,2.7.0)", | |||
org.eclipse.jgit.annotations;version="[6.0.0,6.1.0)", | |||
org.eclipse.jgit.api;version="[6.0.0,6.1.0)", | |||
org.eclipse.jgit.api.errors;version="[6.0.0,6.1.0)", |
@@ -0,0 +1,9 @@ | |||
<?xml version="1.0" encoding="UTF-8" ?> | |||
<FindBugsFilter> | |||
<!-- Silence returning null for Boolean return type --> | |||
<Match> | |||
<Class name="org.eclipse.jgit.junit.ssh.SshTestGitServer$FakeUserAuthGSS" /> | |||
<Method name="doAuth" /> | |||
<Bug pattern="NP_BOOLEAN_RETURN_NULL" /> | |||
</Match> | |||
</FindBugsFilter> |
@@ -0,0 +1,71 @@ | |||
/* | |||
* Copyright (C) 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others | |||
* | |||
* This program and the accompanying materials are made available under the | |||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||
* https://www.eclipse.org/org/documents/edl-v10.php. | |||
* | |||
* SPDX-License-Identifier: BSD-3-Clause | |||
*/ | |||
package org.eclipse.jgit.junit.ssh; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertFalse; | |||
import static org.junit.Assert.assertTrue; | |||
import java.io.File; | |||
import org.eclipse.jgit.api.Git; | |||
import org.junit.Test; | |||
/** | |||
* Some minimal cloning and fetching tests. Concrete subclasses can implement | |||
* the abstract operations from {@link SshTestHarness} to run with different SSH | |||
* implementations. | |||
*/ | |||
public abstract class SshBasicTestBase extends SshTestHarness { | |||
protected File defaultCloneDir; | |||
@Override | |||
public void setUp() throws Exception { | |||
super.setUp(); | |||
defaultCloneDir = new File(getTemporaryDirectory(), "cloned"); | |||
} | |||
@Test | |||
public void testSshCloneWithConfig() throws Exception { | |||
cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, // | |||
"Host localhost", // | |||
"HostName localhost", // | |||
"Port " + testPort, // | |||
"User " + TEST_USER, // | |||
"IdentityFile " + privateKey1.getAbsolutePath()); | |||
} | |||
@Test | |||
public void testSshFetchWithConfig() throws Exception { | |||
File localClone = cloneWith("ssh://localhost/doesntmatter", | |||
defaultCloneDir, null, // | |||
"Host localhost", // | |||
"HostName localhost", // | |||
"Port " + testPort, // | |||
"User " + TEST_USER, // | |||
"IdentityFile " + privateKey1.getAbsolutePath()); | |||
// Do a commit in the upstream repo | |||
try (Git git = new Git(db)) { | |||
writeTrashFile("SomeOtherFile.txt", "Other commit"); | |||
git.add().addFilepattern("SomeOtherFile.txt").call(); | |||
git.commit().setMessage("New commit").call(); | |||
} | |||
// Pull in the clone | |||
try (Git git = Git.open(localClone)) { | |||
File f = new File(git.getRepository().getWorkTree(), | |||
"SomeOtherFile.txt"); | |||
assertFalse(f.exists()); | |||
git.pull().setRemote("origin").call(); | |||
assertTrue(f.exists()); | |||
assertEquals("Other commit", read(f)); | |||
} | |||
} | |||
} |
@@ -40,7 +40,7 @@ import org.junit.experimental.theories.Theory; | |||
* abstract operations from {@link SshTestHarness}. This gives a way to test | |||
* different ssh clients against a unified test suite. | |||
*/ | |||
public abstract class SshTestBase extends SshTestHarness { | |||
public abstract class SshTestBase extends SshBasicTestBase { | |||
@DataPoints | |||
public static String[] KEY_RESOURCES = { // | |||
@@ -65,14 +65,6 @@ public abstract class SshTestBase extends SshTestHarness { | |||
"id_ed25519_testpass", // | |||
"id_ed25519_expensive_testpass" }; | |||
protected File defaultCloneDir; | |||
@Override | |||
public void setUp() throws Exception { | |||
super.setUp(); | |||
defaultCloneDir = new File(getTemporaryDirectory(), "cloned"); | |||
} | |||
@Test | |||
public void testSshWithoutConfig() throws Exception { | |||
assertThrows(TransportException.class, | |||
@@ -132,16 +124,6 @@ public abstract class SshTestBase extends SshTestHarness { | |||
+ "/doesntmatter", defaultCloneDir, null); | |||
} | |||
@Test | |||
public void testSshWithConfig() throws Exception { | |||
cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, // | |||
"Host localhost", // | |||
"HostName localhost", // | |||
"Port " + testPort, // | |||
"User " + TEST_USER, // | |||
"IdentityFile " + privateKey1.getAbsolutePath()); | |||
} | |||
@Test | |||
public void testSshWithConfigEncryptedUnusedKey() throws Exception { | |||
// Copy the encrypted test key from the bundle. |
@@ -9,6 +9,9 @@ | |||
*/ | |||
package org.eclipse.jgit.junit.ssh; | |||
import static org.apache.sshd.core.CoreModuleProperties.SERVER_EXTRA_IDENTIFICATION_LINES; | |||
import static org.apache.sshd.core.CoreModuleProperties.SERVER_EXTRA_IDENT_LINES_SEPARATOR; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
@@ -21,26 +24,28 @@ import java.security.KeyPair; | |||
import java.security.PublicKey; | |||
import java.text.MessageFormat; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import java.util.Locale; | |||
import java.util.concurrent.TimeUnit; | |||
import org.apache.sshd.common.NamedFactory; | |||
import org.apache.sshd.common.NamedResource; | |||
import org.apache.sshd.common.PropertyResolver; | |||
import org.apache.sshd.common.PropertyResolverUtils; | |||
import org.apache.sshd.common.SshConstants; | |||
import org.apache.sshd.common.config.keys.AuthorizedKeyEntry; | |||
import org.apache.sshd.common.config.keys.KeyUtils; | |||
import org.apache.sshd.common.config.keys.PublicKeyEntryResolver; | |||
import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory; | |||
import org.apache.sshd.common.session.Session; | |||
import org.apache.sshd.common.signature.BuiltinSignatures; | |||
import org.apache.sshd.common.signature.Signature; | |||
import org.apache.sshd.common.util.buffer.Buffer; | |||
import org.apache.sshd.common.util.security.SecurityUtils; | |||
import org.apache.sshd.common.util.threads.CloseableExecutorService; | |||
import org.apache.sshd.common.util.threads.ThreadUtils; | |||
import org.apache.sshd.server.ServerAuthenticationManager; | |||
import org.apache.sshd.server.ServerFactoryManager; | |||
import org.apache.sshd.server.ServerBuilder; | |||
import org.apache.sshd.server.SshServer; | |||
import org.apache.sshd.server.auth.UserAuth; | |||
import org.apache.sshd.server.auth.UserAuthFactory; | |||
@@ -52,8 +57,9 @@ import org.apache.sshd.server.command.AbstractCommandSupport; | |||
import org.apache.sshd.server.session.ServerSession; | |||
import org.apache.sshd.server.shell.UnknownCommand; | |||
import org.apache.sshd.server.subsystem.SubsystemFactory; | |||
import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory; | |||
import org.apache.sshd.sftp.server.SftpSubsystemFactory; | |||
import org.eclipse.jgit.annotations.NonNull; | |||
import org.eclipse.jgit.annotations.Nullable; | |||
import org.eclipse.jgit.lib.Repository; | |||
import org.eclipse.jgit.transport.ReceivePack; | |||
import org.eclipse.jgit.transport.RemoteConfig; | |||
@@ -161,7 +167,9 @@ public class SshTestGitServer { | |||
this.testUser = testUser; | |||
setTestUserPublicKey(testKey); | |||
this.repository = repository; | |||
server = SshServer.setUpDefaultServer(); | |||
ServerBuilder builder = ServerBuilder.builder() | |||
.signatureFactories(getSignatureFactories()); | |||
server = builder.build(); | |||
hostKeys.add(hostKey); | |||
server.setKeyPairProvider((session) -> hostKeys); | |||
@@ -186,6 +194,37 @@ public class SshTestGitServer { | |||
}); | |||
} | |||
/** | |||
* Apache MINA sshd 2.6.0 has removed DSA, DSA_CERT and RSA_CERT. We have to | |||
* set it up explicitly to still allow users to connect with DSA keys. | |||
* | |||
* @return a list of supported signature factories | |||
*/ | |||
@SuppressWarnings("deprecation") | |||
private static List<NamedFactory<Signature>> getSignatureFactories() { | |||
// @formatter:off | |||
return Arrays.asList( | |||
BuiltinSignatures.nistp256_cert, | |||
BuiltinSignatures.nistp384_cert, | |||
BuiltinSignatures.nistp521_cert, | |||
BuiltinSignatures.ed25519_cert, | |||
BuiltinSignatures.rsaSHA512_cert, | |||
BuiltinSignatures.rsaSHA256_cert, | |||
BuiltinSignatures.rsa_cert, | |||
BuiltinSignatures.nistp256, | |||
BuiltinSignatures.nistp384, | |||
BuiltinSignatures.nistp521, | |||
BuiltinSignatures.ed25519, | |||
BuiltinSignatures.sk_ecdsa_sha2_nistp256, | |||
BuiltinSignatures.sk_ssh_ed25519, | |||
BuiltinSignatures.rsaSHA512, | |||
BuiltinSignatures.rsaSHA256, | |||
BuiltinSignatures.rsa, | |||
BuiltinSignatures.dsa_cert, | |||
BuiltinSignatures.dsa); | |||
// @formatter:on | |||
} | |||
private static PublicKey readPublicKey(Path key) | |||
throws IOException, GeneralSecurityException { | |||
return AuthorizedKeyEntry.readAuthorizedKeys(key).get(0) | |||
@@ -202,7 +241,7 @@ public class SshTestGitServer { | |||
private static class FakeUserAuthGSS extends UserAuthGSS { | |||
@Override | |||
protected Boolean doAuth(Buffer buffer, boolean initial) | |||
protected @Nullable Boolean doAuth(Buffer buffer, boolean initial) | |||
throws Exception { | |||
// We always reply that we did do this, but then we fail at the | |||
// first token message. That way we can test that the client-side | |||
@@ -277,14 +316,8 @@ public class SshTestGitServer { | |||
@NonNull | |||
protected List<SubsystemFactory> configureSubsystems() { | |||
// SFTP. | |||
server.setFileSystemFactory(new VirtualFileSystemFactory() { | |||
@Override | |||
protected Path computeRootDir(Session session) throws IOException { | |||
return SshTestGitServer.this.repository.getDirectory() | |||
.getParentFile().getAbsoluteFile().toPath(); | |||
} | |||
}); | |||
server.setFileSystemFactory(new VirtualFileSystemFactory(repository | |||
.getDirectory().getParentFile().getAbsoluteFile().toPath())); | |||
return Collections | |||
.singletonList((new SftpSubsystemFactory.Builder()).build()); | |||
} | |||
@@ -433,9 +466,8 @@ public class SshTestGitServer { | |||
*/ | |||
public void setPreamble(String... lines) { | |||
if (lines != null && lines.length > 0) { | |||
PropertyResolverUtils.updateProperty(this.server, | |||
ServerFactoryManager.SERVER_EXTRA_IDENTIFICATION_LINES, | |||
String.join("|", lines)); | |||
SERVER_EXTRA_IDENTIFICATION_LINES.set(server, String.join( | |||
String.valueOf(SERVER_EXTRA_IDENT_LINES_SEPARATOR), lines)); | |||
} | |||
} | |||
@@ -9,10 +9,10 @@ | |||
*/ | |||
package org.eclipse.jgit.junit; | |||
import static java.lang.ClassLoader.getSystemClassLoader; | |||
import java.net.MalformedURLException; | |||
import java.net.URL; | |||
import java.net.URLClassLoader; | |||
import java.nio.file.Paths; | |||
import org.junit.runners.BlockJUnit4ClassRunner; | |||
import org.junit.runners.model.InitializationError; | |||
@@ -40,7 +40,13 @@ public class SeparateClassloaderTestRunner extends BlockJUnit4ClassRunner { | |||
private static Class<?> loadNewClass(Class<?> klass) | |||
throws InitializationError { | |||
try { | |||
URL[] urls = ((URLClassLoader) getSystemClassLoader()).getURLs(); | |||
String pathSeparator = System.getProperty("path.separator"); | |||
String[] classPathEntries = System.getProperty("java.class.path") | |||
.split(pathSeparator); | |||
URL[] urls = new URL[classPathEntries.length]; | |||
for (int i = 0; i < classPathEntries.length; i++) { | |||
urls[i] = Paths.get(classPathEntries[i]).toUri().toURL(); | |||
} | |||
ClassLoader testClassLoader = new URLClassLoader(urls) { | |||
@Override | |||
@@ -54,7 +60,7 @@ public class SeparateClassloaderTestRunner extends BlockJUnit4ClassRunner { | |||
} | |||
}; | |||
return Class.forName(klass.getName(), true, testClassLoader); | |||
} catch (ClassNotFoundException e) { | |||
} catch (ClassNotFoundException | MalformedURLException e) { | |||
throw new InitializationError(e); | |||
} | |||
} |
@@ -43,7 +43,7 @@ import org.eclipse.jgit.errors.ObjectWritingException; | |||
import org.eclipse.jgit.internal.storage.file.FileRepository; | |||
import org.eclipse.jgit.internal.storage.file.LockFile; | |||
import org.eclipse.jgit.internal.storage.file.ObjectDirectory; | |||
import org.eclipse.jgit.internal.storage.file.PackFile; | |||
import org.eclipse.jgit.internal.storage.file.Pack; | |||
import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry; | |||
import org.eclipse.jgit.internal.storage.pack.PackWriter; | |||
import org.eclipse.jgit.lib.AnyObjectId; | |||
@@ -773,7 +773,7 @@ public class TestRepository<R extends Repository> implements AutoCloseable { | |||
rw.writeInfoRefs(); | |||
final StringBuilder w = new StringBuilder(); | |||
for (PackFile p : fr.getObjectDatabase().getPacks()) { | |||
for (Pack p : fr.getObjectDatabase().getPacks()) { | |||
w.append("P "); | |||
w.append(p.getPackFile().getName()); | |||
w.append('\n'); | |||
@@ -954,7 +954,7 @@ public class TestRepository<R extends Repository> implements AutoCloseable { | |||
} | |||
private static void prunePacked(ObjectDirectory odb) throws IOException { | |||
for (PackFile p : odb.getPacks()) { | |||
for (Pack p : odb.getPacks()) { | |||
for (MutableEntry e : p) | |||
FileUtils.delete(odb.fileFor(e.toObjectId())); | |||
} |
@@ -9,11 +9,11 @@ Bundle-Localization: plugin | |||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8 | |||
Import-Package: javax.servlet;version="[3.1.0,4.0.0)", | |||
javax.servlet.http;version="[3.1.0,4.0.0)", | |||
org.apache.http;version="[4.3.0,5.0.0)", | |||
org.apache.http.client;version="[4.3.0,5.0.0)", | |||
org.apache.http.client.methods;version="[4.3.0,5.0.0)", | |||
org.apache.http.entity;version="[4.3.0,5.0.0)", | |||
org.apache.http.impl.client;version="[4.3.0,5.0.0)", | |||
org.apache.http;version="[4.4.0,5.0.0)", | |||
org.apache.http.client;version="[4.4.0,5.0.0)", | |||
org.apache.http.client.methods;version="[4.4.0,5.0.0)", | |||
org.apache.http.entity;version="[4.4.0,5.0.0)", | |||
org.apache.http.impl.client;version="[4.4.0,5.0.0)", | |||
org.eclipse.jetty.continuation;version="[9.4.5,10.0.0)", | |||
org.eclipse.jetty.http;version="[9.4.5,10.0.0)", | |||
org.eclipse.jetty.io;version="[9.4.5,10.0.0)", |
@@ -24,7 +24,6 @@ Import-Package: com.google.gson;version="[2.8.0,3.0.0)", | |||
javax.servlet.annotation;version="[3.1.0,4.0.0)", | |||
javax.servlet.http;version="[3.1.0,4.0.0)", | |||
org.apache.http;version="[4.3.0,5.0.0)", | |||
org.apache.http.client;version="[4.3.0,5.0.0)", | |||
org.eclipse.jgit.annotations;version="[6.0.0,6.1.0)", | |||
org.eclipse.jgit.internal;version="[6.0.0,6.1.0)", | |||
org.eclipse.jgit.internal.storage.file;version="[6.0.0,6.1.0)", |
@@ -0,0 +1,8 @@ | |||
<?xml version="1.0" encoding="UTF-8" ?> | |||
<FindBugsFilter> | |||
<!-- Field is written by gson, seems like spotbugs doesn't recognize this --> | |||
<Match> | |||
<Class name="org.eclipse.jgit.lfs.server.LfsObject" /> | |||
<Bug pattern="UWF_UNWRITTEN_FIELD" /> | |||
</Match> | |||
</FindBugsFilter> |
@@ -51,8 +51,8 @@ org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTags=error | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=no_tag | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTags=ignore | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled | |||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private |
@@ -12,7 +12,13 @@ package org.eclipse.jgit.lfs.lib; | |||
import static java.nio.charset.StandardCharsets.UTF_8; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertFalse; | |||
import static org.junit.Assert.assertNotEquals; | |||
import static org.junit.Assert.assertNull; | |||
import static org.junit.Assert.assertThrows; | |||
import static org.junit.Assert.assertTrue; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.IOException; | |||
@@ -23,17 +29,275 @@ import org.junit.Test; | |||
* Test LfsPointer file abstraction | |||
*/ | |||
public class LFSPointerTest { | |||
private static final String TEST_SHA256 = "27e15b72937fc8f558da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b10"; | |||
@Test | |||
public void testEncoding() throws IOException { | |||
final String s = "27e15b72937fc8f558da24ac3d50ec20302a4cf21e33b87ae8e4ce90e89c4b10"; | |||
AnyLongObjectId id = LongObjectId.fromString(s); | |||
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256); | |||
LfsPointer ptr = new LfsPointer(id, 4); | |||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { | |||
ptr.encode(baos); | |||
assertEquals( | |||
"version https://git-lfs.github.com/spec/v1\noid sha256:" | |||
+ s + "\nsize 4\n", | |||
+ TEST_SHA256 + "\nsize 4\n", | |||
baos.toString(UTF_8.name())); | |||
} | |||
} | |||
@Test | |||
public void testReadValidLfsPointer() throws Exception { | |||
String ptr = "version https://git-lfs.github.com/spec/v1\n" | |||
+ "oid sha256:" + TEST_SHA256 + '\n' | |||
+ "size 4\n"; | |||
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256); | |||
LfsPointer lfs = new LfsPointer(id, 4); | |||
try (ByteArrayInputStream in = new ByteArrayInputStream( | |||
ptr.getBytes(UTF_8))) { | |||
assertEquals(lfs, LfsPointer.parseLfsPointer(in)); | |||
} | |||
} | |||
@Test | |||
public void testReadValidLfsPointerUnordered() throws Exception { | |||
// This is actually not allowed per the spec, but JGit accepts it | |||
// anyway. | |||
String ptr = "version https://git-lfs.github.com/spec/v1\n" | |||
+ "size 4\n" | |||
+ "oid sha256:" + TEST_SHA256 + '\n'; | |||
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256); | |||
LfsPointer lfs = new LfsPointer(id, 4); | |||
try (ByteArrayInputStream in = new ByteArrayInputStream( | |||
ptr.getBytes(UTF_8))) { | |||
assertEquals(lfs, LfsPointer.parseLfsPointer(in)); | |||
} | |||
} | |||
@Test | |||
public void testReadValidLfsPointerVersionNotFirst() throws Exception { | |||
// This is actually not allowed per the spec, but JGit accepts it | |||
// anyway. | |||
String ptr = "oid sha256:" + TEST_SHA256 + '\n' | |||
+ "size 4\n" | |||
+ "version https://git-lfs.github.com/spec/v1\n"; | |||
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256); | |||
LfsPointer lfs = new LfsPointer(id, 4); | |||
try (ByteArrayInputStream in = new ByteArrayInputStream( | |||
ptr.getBytes(UTF_8))) { | |||
assertEquals(lfs, LfsPointer.parseLfsPointer(in)); | |||
} | |||
} | |||
@Test | |||
public void testReadInvalidLfsPointer() throws Exception { | |||
String cSource = "size_t someFunction(void *ptr); // Fake C source\n"; | |||
try (ByteArrayInputStream in = new ByteArrayInputStream( | |||
cSource.getBytes(UTF_8))) { | |||
assertNull("Is not a LFS pointer", LfsPointer.parseLfsPointer(in)); | |||
} | |||
} | |||
@Test | |||
public void testReadInvalidLfsPointer2() throws Exception { | |||
String cSource = "size_t\nsomeFunction(void *ptr);\n// Fake C source\n"; | |||
try (ByteArrayInputStream in = new ByteArrayInputStream( | |||
cSource.getBytes(UTF_8))) { | |||
assertNull("Is not a LFS pointer", LfsPointer.parseLfsPointer(in)); | |||
} | |||
} | |||
@Test | |||
public void testReadInValidLfsPointerVersionWrong() throws Exception { | |||
String ptr = "version https://git-lfs.example.org/spec/v1\n" | |||
+ "oid sha256:" + TEST_SHA256 + '\n' | |||
+ "size 4\n"; | |||
try (ByteArrayInputStream in = new ByteArrayInputStream( | |||
ptr.getBytes(UTF_8))) { | |||
assertNull("Is not a LFS pointer", LfsPointer.parseLfsPointer(in)); | |||
} | |||
} | |||
@Test | |||
public void testReadInValidLfsPointerVersionTwice() throws Exception { | |||
String ptr = "version https://git-lfs.github.com/spec/v1\n" | |||
+ "version https://git-lfs.github.com/spec/v1\n" | |||
+ "oid sha256:" + TEST_SHA256 + '\n' | |||
+ "size 4\n"; | |||
try (ByteArrayInputStream in = new ByteArrayInputStream( | |||
ptr.getBytes(UTF_8))) { | |||
assertNull("Is not a LFS pointer", LfsPointer.parseLfsPointer(in)); | |||
} | |||
} | |||
@Test | |||
public void testReadInValidLfsPointerVersionTwice2() throws Exception { | |||
String ptr = "version https://git-lfs.github.com/spec/v1\n" | |||
+ "oid sha256:" + TEST_SHA256 + '\n' | |||
+ "version https://git-lfs.github.com/spec/v1\n" | |||
+ "size 4\n"; | |||
try (ByteArrayInputStream in = new ByteArrayInputStream( | |||
ptr.getBytes(UTF_8))) { | |||
assertNull("Is not a LFS pointer", LfsPointer.parseLfsPointer(in)); | |||
} | |||
} | |||
@Test | |||
public void testReadInValidLfsPointerOidTwice() throws Exception { | |||
String ptr = "version https://git-lfs.github.com/spec/v1\n" | |||
+ "oid sha256:" + TEST_SHA256 + '\n' | |||
+ "oid sha256:" + TEST_SHA256 + '\n' | |||
+ "size 4\n"; | |||
try (ByteArrayInputStream in = new ByteArrayInputStream( | |||
ptr.getBytes(UTF_8))) { | |||
assertNull("Is not a LFS pointer", LfsPointer.parseLfsPointer(in)); | |||
} | |||
} | |||
@Test | |||
public void testReadInValidLfsPointerSizeTwice() throws Exception { | |||
String ptr = "version https://git-lfs.github.com/spec/v1\n" | |||
+ "size 4\n" | |||
+ "size 4\n" | |||
+ "oid sha256:" + TEST_SHA256 + '\n'; | |||
try (ByteArrayInputStream in = new ByteArrayInputStream( | |||
ptr.getBytes(UTF_8))) { | |||
assertNull("Is not a LFS pointer", LfsPointer.parseLfsPointer(in)); | |||
} | |||
} | |||
@Test | |||
public void testRoundtrip() throws Exception { | |||
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256); | |||
LfsPointer ptr = new LfsPointer(id, 4); | |||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { | |||
ptr.encode(baos); | |||
try (ByteArrayInputStream in = new ByteArrayInputStream( | |||
baos.toByteArray())) { | |||
assertEquals(ptr, LfsPointer.parseLfsPointer(in)); | |||
} | |||
} | |||
} | |||
@Test | |||
public void testEquals() throws Exception { | |||
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256); | |||
LfsPointer lfs = new LfsPointer(id, 4); | |||
AnyLongObjectId id2 = LongObjectId.fromString(TEST_SHA256); | |||
LfsPointer lfs2 = new LfsPointer(id2, 4); | |||
assertTrue(lfs.equals(lfs2)); | |||
assertTrue(lfs2.equals(lfs)); | |||
} | |||
@Test | |||
public void testEqualsNull() throws Exception { | |||
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256); | |||
LfsPointer lfs = new LfsPointer(id, 4); | |||
assertFalse(lfs.equals(null)); | |||
} | |||
@Test | |||
public void testEqualsSame() throws Exception { | |||
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256); | |||
LfsPointer lfs = new LfsPointer(id, 4); | |||
assertTrue(lfs.equals(lfs)); | |||
} | |||
@Test | |||
public void testEqualsOther() throws Exception { | |||
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256); | |||
LfsPointer lfs = new LfsPointer(id, 4); | |||
assertFalse(lfs.equals(new Object())); | |||
} | |||
@Test | |||
public void testNotEqualsOid() throws Exception { | |||
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256); | |||
LfsPointer lfs = new LfsPointer(id, 4); | |||
AnyLongObjectId id2 = LongObjectId | |||
.fromString(TEST_SHA256.replace('7', '5')); | |||
LfsPointer lfs2 = new LfsPointer(id2, 4); | |||
assertFalse(lfs.equals(lfs2)); | |||
assertFalse(lfs2.equals(lfs)); | |||
} | |||
@Test | |||
public void testNotEqualsSize() throws Exception { | |||
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256); | |||
LfsPointer lfs = new LfsPointer(id, 4); | |||
AnyLongObjectId id2 = LongObjectId.fromString(TEST_SHA256); | |||
LfsPointer lfs2 = new LfsPointer(id2, 5); | |||
assertFalse(lfs.equals(lfs2)); | |||
assertFalse(lfs2.equals(lfs)); | |||
} | |||
@Test | |||
public void testCompareToEquals() throws Exception { | |||
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256); | |||
LfsPointer lfs = new LfsPointer(id, 4); | |||
AnyLongObjectId id2 = LongObjectId.fromString(TEST_SHA256); | |||
LfsPointer lfs2 = new LfsPointer(id2, 4); | |||
assertEquals(0, lfs.compareTo(lfs2)); | |||
assertEquals(0, lfs2.compareTo(lfs)); | |||
} | |||
@Test | |||
@SuppressWarnings("SelfComparison") | |||
public void testCompareToSame() throws Exception { | |||
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256); | |||
LfsPointer lfs = new LfsPointer(id, 4); | |||
assertEquals(0, lfs.compareTo(lfs)); | |||
} | |||
@Test | |||
public void testCompareToNotEqualsOid() throws Exception { | |||
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256); | |||
LfsPointer lfs = new LfsPointer(id, 4); | |||
AnyLongObjectId id2 = LongObjectId | |||
.fromString(TEST_SHA256.replace('7', '5')); | |||
LfsPointer lfs2 = new LfsPointer(id2, 4); | |||
assertNotEquals(0, lfs.compareTo(lfs2)); | |||
assertNotEquals(0, lfs2.compareTo(lfs)); | |||
} | |||
@Test | |||
public void testCompareToNotEqualsSize() throws Exception { | |||
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256); | |||
LfsPointer lfs = new LfsPointer(id, 4); | |||
AnyLongObjectId id2 = LongObjectId.fromString(TEST_SHA256); | |||
LfsPointer lfs2 = new LfsPointer(id2, 5); | |||
assertNotEquals(0, lfs.compareTo(lfs2)); | |||
assertNotEquals(0, lfs2.compareTo(lfs)); | |||
} | |||
@Test | |||
public void testCompareToNull() throws Exception { | |||
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256); | |||
LfsPointer lfs = new LfsPointer(id, 4); | |||
assertThrows(NullPointerException.class, () -> lfs.compareTo(null)); | |||
} | |||
@Test | |||
public void testHashcodeEquals() throws Exception { | |||
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256); | |||
LfsPointer lfs = new LfsPointer(id, 4); | |||
AnyLongObjectId id2 = LongObjectId.fromString(TEST_SHA256); | |||
LfsPointer lfs2 = new LfsPointer(id2, 4); | |||
assertEquals(lfs.hashCode(), lfs2.hashCode()); | |||
} | |||
@Test | |||
public void testHashcodeSame() throws Exception { | |||
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256); | |||
LfsPointer lfs = new LfsPointer(id, 4); | |||
assertEquals(lfs.hashCode(), lfs.hashCode()); | |||
} | |||
@Test | |||
public void testHashcodeNotEquals() throws Exception { | |||
AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256); | |||
LfsPointer lfs = new LfsPointer(id, 4); | |||
AnyLongObjectId id2 = LongObjectId.fromString(TEST_SHA256); | |||
LfsPointer lfs2 = new LfsPointer(id2, 5); | |||
assertNotEquals(lfs.hashCode(), lfs2.hashCode()); | |||
} | |||
} |
@@ -13,8 +13,6 @@ Export-Package: org.eclipse.jgit.lfs;version="6.0.0", | |||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8 | |||
Import-Package: com.google.gson;version="[2.8.2,3.0.0)", | |||
com.google.gson.stream;version="[2.8.2,3.0.0)", | |||
org.apache.http.impl.client;version="[4.2.6,5.0.0)", | |||
org.apache.http.impl.conn;version="[4.2.6,5.0.0)", | |||
org.eclipse.jgit.annotations;version="[6.0.0,6.1.0)";resolution:=optional, | |||
org.eclipse.jgit.api.errors;version="[6.0.0,6.1.0)", | |||
org.eclipse.jgit.attributes;version="[6.0.0,6.1.0)", |
@@ -1,5 +1,5 @@ | |||
/* | |||
* Copyright (C) 2016, Christian Halstrick <christian.halstrick@sap.com> and others | |||
* Copyright (C) 2016, 2021 Christian Halstrick <christian.halstrick@sap.com> and others | |||
* | |||
* This program and the accompanying materials are made available under the | |||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||
@@ -19,6 +19,7 @@ import java.io.OutputStream; | |||
import java.io.PrintStream; | |||
import java.io.UnsupportedEncodingException; | |||
import java.util.Locale; | |||
import java.util.Objects; | |||
import org.eclipse.jgit.annotations.Nullable; | |||
import org.eclipse.jgit.lfs.lib.AnyLongObjectId; | |||
@@ -56,9 +57,9 @@ public class LfsPointer implements Comparable<LfsPointer> { | |||
public static final String HASH_FUNCTION_NAME = Constants.LONG_HASH_FUNCTION | |||
.toLowerCase(Locale.ROOT).replace("-", ""); //$NON-NLS-1$ //$NON-NLS-2$ | |||
private AnyLongObjectId oid; | |||
private final AnyLongObjectId oid; | |||
private long size; | |||
private final long size; | |||
/** | |||
* <p>Constructor for LfsPointer.</p> | |||
@@ -129,19 +130,49 @@ public class LfsPointer implements Comparable<LfsPointer> { | |||
LongObjectId id = null; | |||
long sz = -1; | |||
// This parsing is a bit too general if we go by the spec at | |||
// https://github.com/git-lfs/git-lfs/blob/master/docs/spec.md | |||
// Comment lines are not mentioned in the spec, and the "version" line | |||
// MUST be the first. | |||
try (BufferedReader br = new BufferedReader( | |||
new InputStreamReader(in, UTF_8))) { | |||
for (String s = br.readLine(); s != null; s = br.readLine()) { | |||
if (s.startsWith("#") || s.length() == 0) { //$NON-NLS-1$ | |||
continue; | |||
} else if (s.startsWith("version") && s.length() > 8 //$NON-NLS-1$ | |||
&& (s.substring(8).trim().equals(VERSION) || | |||
s.substring(8).trim().equals(VERSION_LEGACY))) { | |||
versionLine = true; | |||
} else if (s.startsWith("oid sha256:")) { //$NON-NLS-1$ | |||
id = LongObjectId.fromString(s.substring(11).trim()); | |||
} else if (s.startsWith("size") && s.length() > 5) { //$NON-NLS-1$ | |||
sz = Long.parseLong(s.substring(5).trim()); | |||
} else if (s.startsWith("version")) { //$NON-NLS-1$ | |||
if (versionLine || s.length() < 8 || s.charAt(7) != ' ') { | |||
return null; // Not a LFS pointer | |||
} | |||
String rest = s.substring(8).trim(); | |||
versionLine = VERSION.equals(rest) | |||
|| VERSION_LEGACY.equals(rest); | |||
if (!versionLine) { | |||
return null; // Not a LFS pointer | |||
} | |||
} else { | |||
try { | |||
if (s.startsWith("oid sha256:")) { //$NON-NLS-1$ | |||
if (id != null) { | |||
return null; // Not a LFS pointer | |||
} | |||
id = LongObjectId | |||
.fromString(s.substring(11).trim()); | |||
} else if (s.startsWith("size")) { //$NON-NLS-1$ | |||
if (sz > 0 || s.length() < 5 | |||
|| s.charAt(4) != ' ') { | |||
return null; // Not a LFS pointer | |||
} | |||
sz = Long.parseLong(s.substring(5).trim()); | |||
} | |||
} catch (RuntimeException e) { | |||
// We could not parse the line. If we have a version | |||
// already, this is a corrupt LFS pointer. Otherwise it | |||
// is just not an LFS pointer. | |||
if (versionLine) { | |||
throw e; | |||
} | |||
return null; | |||
} | |||
} | |||
} | |||
if (versionLine && id != null && sz > -1) { | |||
@@ -170,5 +201,22 @@ public class LfsPointer implements Comparable<LfsPointer> { | |||
return Long.compare(getSize(), o.getSize()); | |||
} | |||
} | |||
@Override | |||
public int hashCode() { | |||
return Objects.hash(getOid()) * 31 + Long.hashCode(getSize()); | |||
} | |||
@Override | |||
public boolean equals(Object obj) { | |||
if (this == obj) { | |||
return true; | |||
} | |||
if (obj == null || getClass() != obj.getClass()) { | |||
return false; | |||
} | |||
LfsPointer other = (LfsPointer) obj; | |||
return Objects.equals(getOid(), other.getOid()) | |||
&& getSize() == other.getSize(); | |||
} | |||
} |