diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index efe61052b..50aae7390 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -189,6 +189,7 @@ errorDecodingFromFile=Error decoding from file {0} errorEncodingFromFile=Error encoding from file {0} errorInBase64CodeReadingStream=Error in Base64 code reading stream. errorInPackedRefs=error in packed-refs +errorInvalidPushCert=error: invalid protocol: {0} errorInvalidProtocolWantedOldNewRef=error: invalid protocol: wanted 'old new ref' errorListing=Error listing {0} errorOccurredDuringUnpackingOnTheRemoteEnd=error occurred during unpacking on the remote end: {0} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java index 8c81c9513..12893d6ff 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -248,6 +248,7 @@ public static JGitText get() { /***/ public String errorEncodingFromFile; /***/ public String errorInBase64CodeReadingStream; /***/ public String errorInPackedRefs; + /***/ public String errorInvalidPushCert; /***/ public String errorInvalidProtocolWantedOldNewRef; /***/ public String errorListing; /***/ public String errorOccurredDuringUnpackingOnTheRemoteEnd; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java index 0475d2792..dfb8ca93a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java @@ -246,6 +246,15 @@ public Set getCapabilities() { /** The size of the received pack, including index size */ private Long packSize; + PushCertificateParser pushCertificateParser; + + /** + * @return the push certificate used to verify the pushers identity. + */ + PushCertificate getPushCertificate() { + return pushCertificateParser; + } + /** * Create a new pack receive for an open repository. * @@ -267,6 +276,7 @@ protected BaseReceivePack(final Repository into) { refFilter = RefFilter.DEFAULT; advertisedHaves = new HashSet(); clientShallowCommits = new HashSet(); + pushCertificateParser = new PushCertificateParser(db, cfg); } /** Configuration for receive operations. */ @@ -287,6 +297,9 @@ public ReceiveConfig parse(final Config cfg) { final boolean allowNonFastForwards; final boolean allowOfsDelta; + final String certNonceSeed; + final int certNonceSlopLimit; + ReceiveConfig(final Config config) { checkReceivedObjects = config.getBoolean( "receive", "fsckobjects", //$NON-NLS-1$ //$NON-NLS-2$ @@ -304,6 +317,8 @@ public ReceiveConfig parse(final Config cfg) { "denynonfastforwards", false); //$NON-NLS-1$ allowOfsDelta = config.getBoolean("repack", "usedeltabaseoffset", //$NON-NLS-1$ //$NON-NLS-2$ true); + certNonceSeed = config.getString("receive", null, "certnonceseed"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + certNonceSlopLimit = config.getInt("receive", "certnonceslop", 0); //$NON-NLS-1$ //$NON-NLS-2$ } ObjectChecker newObjectChecker() { @@ -929,6 +944,9 @@ public void sendAdvertisedRefs(final RefAdvertiser adv) adv.advertiseCapability(CAPABILITY_SIDE_BAND_64K); adv.advertiseCapability(CAPABILITY_DELETE_REFS); adv.advertiseCapability(CAPABILITY_REPORT_STATUS); + if (pushCertificateParser.enabled()) + adv.advertiseCapability( + pushCertificateParser.getAdvertiseNonce()); if (db.getRefDatabase().performsAtomicTransactions()) adv.advertiseCapability(CAPABILITY_ATOMIC); if (allowOfsDelta) @@ -968,8 +986,18 @@ protected void recvCommands() throws IOException { final FirstLine firstLine = new FirstLine(line); enabledCapabilities = firstLine.getCapabilities(); line = firstLine.getLine(); + + if (line.equals(GitProtocolConstants.OPTION_PUSH_CERT)) + pushCertificateParser.receiveHeader(pckIn, + !isBiDirectionalPipe()); } + if (line.equals("-----BEGIN PGP SIGNATURE-----\n")) //$NON-NLS-1$ + pushCertificateParser.receiveSignature(pckIn); + + if (pushCertificateParser.enabled()) + pushCertificateParser.addCommand(line); + if (line.length() < 83) { final String m = JGitText.get().errorInvalidProtocolWantedOldNewRef; sendError(m); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java index 976a82361..6a4577ed6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java @@ -136,6 +136,13 @@ public class GitProtocolConstants { */ public static final String OPTION_SYMREF = "symref"; //$NON-NLS-1$ + /** + * The client will send a push certificate. + * + * @since 3.7 + */ + public static final String OPTION_PUSH_CERT = "push-cert"; //$NON-NLS-1$ + /** * The client supports atomic pushes. If this option is used, the server * will update all refs within one atomic transaction. @@ -172,6 +179,13 @@ public class GitProtocolConstants { */ public static final String CAPABILITY_SIDE_BAND_64K = "side-band-64k"; //$NON-NLS-1$ + /** + * The server allows recording of push certificates. + * + * @since 3.7 + */ + public static final String CAPABILITY_PUSH_CERT = "push-cert"; //$NON-NLS-1$ + static enum MultiAck { OFF, CONTINUE, DETAILED; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java new file mode 100644 index 000000000..1e8da20b0 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2015, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - 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. + * + * - 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. + * + * 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. + */ +package org.eclipse.jgit.transport; + +import java.io.UnsupportedEncodingException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import org.eclipse.jgit.internal.storage.dfs.DfsRepository; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.NonceGenerator; +import org.eclipse.jgit.transport.PushCertificate.NonceStatus; + +/** + * The nonce generator which was first introduced to git-core. + */ +public class HMACSHA1NonceGenerator implements NonceGenerator { + + private Mac mac; + + /** + * @param seed + * @throws IllegalStateException + */ + public HMACSHA1NonceGenerator(String seed) throws IllegalStateException { + try { + byte[] keyBytes = seed.getBytes("ISO-8859-1"); //$NON-NLS-1$ + SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA1"); //$NON-NLS-1$ + mac = Mac.getInstance("HmacSHA1"); //$NON-NLS-1$ + mac.init(signingKey); + } catch (InvalidKeyException e) { + throw new IllegalStateException(e); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException(e); + } + } + + public synchronized String createNonce(Repository repo, long timestamp) + throws IllegalStateException { + String path; + if (repo instanceof DfsRepository) + path = ((DfsRepository) repo).getDescription().getRepositoryName(); + else if (repo.getDirectory() != null) + path = repo.getDirectory().getPath(); + else + throw new IllegalStateException(); + + String input = path + ":" + String.valueOf(timestamp); //$NON-NLS-1$ + byte[] rawHmac; + try { + rawHmac = mac.doFinal(input.getBytes("UTF-8")); //$NON-NLS-1$ + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException(e); + } + String sentNonce = String.format( + "%d-%20X", new Long(timestamp), rawHmac); //$NON-NLS-1$ + return sentNonce; + } + + @Override + public NonceStatus verify(String received, String sent, + Repository db, boolean allowSlop, int slop) { + if (received.isEmpty()) + return NonceStatus.MISSING; + else if (sent.isEmpty()) + return NonceStatus.UNSOLICITED; + else if (received.equals(sent)) + return NonceStatus.OK; + + if (!allowSlop) + return NonceStatus.BAD; + + /* nonce is concat(, "-", ) */ + int idxSent = sent.indexOf('-'); + int idxRecv = received.indexOf('-'); + if (idxSent == -1 || idxRecv == -1) + return NonceStatus.BAD; + + long signedStamp; + long advertisedStamp; + try { + signedStamp = Long.parseLong(received.substring(0, idxRecv)); + advertisedStamp = Long.parseLong(sent.substring(0, idxSent)); + } catch (Exception e) { + return NonceStatus.BAD; + } + + // what we would have signed earlier + String expect = createNonce(db, signedStamp); + + if (!expect.equals(received)) + return NonceStatus.BAD; + + long nonceStampSlop = Math.abs(advertisedStamp - signedStamp); + + if (nonceStampSlop <= slop) { + return NonceStatus.OK; + } else { + return NonceStatus.SLOP; + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NonceGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NonceGenerator.java new file mode 100644 index 000000000..3afacd718 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NonceGenerator.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2015, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - 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. + * + * - 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. + * + * 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. + */ +package org.eclipse.jgit.transport; + +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.PushCertificate.NonceStatus; + +/** + * A NonceGenerator is used to create a nonce to be sent out to the pusher who + * will sign the nonce to prove it is not a replay attack on the push + * certificate. + */ +public interface NonceGenerator { + + /** + * @param db + * The repository which should be used to obtain a unique String + * such that the pusher cannot forge nonces by pushing to another + * repository at the same time as well and reusing the nonce. + * @param timestamp + * The current time in seconds. + * @return The nonce to be signed by the pusher + * @throws IllegalStateException + */ + public String createNonce(Repository db, long timestamp) + throws IllegalStateException; + + /** + * @param received + * The nonce which was received from the server + * @param sent + * The nonce which was originally sent out to the client. + * @param db + * The repository which should be used to obtain a unique String + * such that the pusher cannot forge nonces by pushing to another + * repository at the same time as well and reusing the nonce. + * + * @param allowSlop + * If the receiving backend is is able to generate slop. This is + * the case for serving via http protocol using more than one + * http frontend. The client would talk to different http + * frontends, which may have a slight difference of time due to + * @param slop + * If `allowSlop` is true, this specifies the number of seconds + * which we allow as slop. + * + * @return a NonceStatus indicating the trustworthiness of the received + * nonce. + */ + public NonceStatus verify(String received, String sent, + Repository db, boolean allowSlop, int slop); +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificate.java new file mode 100644 index 000000000..cc47874f9 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificate.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2015, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - 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. + * + * - 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. + * + * 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. + */ + +package org.eclipse.jgit.transport; + +/** + * The required information to verify the push. + */ +public class PushCertificate { + + /** The tuple "name " as presented in the push certificate */ + String pusher; + + /** The remote URL the signed push goes to */ + String pushee; + + /** What we think about the returned signed nonce */ + NonceStatus nonceStatus; + + /** + * + * + */ + public enum NonceStatus { + /** + * + */ + UNSOLICITED, + /** + * + */ + BAD, + /** + * + */ + MISSING, + /** + * + */ + OK, + /** + * + */ + SLOP + } + + /** + * + */ + String commandList; + + /** + * + */ + String signature; + + /** + * + * @return the signature, consisting of the lines received between the lines + * '----BEGIN GPG SIGNATURE-----\n' and the '----END GPG + * SIGNATURE-----\n' + */ + public String getSignature() { + return signature; + } + + /** + * @return the list of commands as one string to be feed into the signature + * verifier. + */ + public String getCommandList() { + return commandList; + } + + /** + * @return the pushedCertPusher + */ + public String getPusher() { + return pusher; + } + + /** + * @return the pushedCertPushee + */ + public String getPushee() { + return pushee; + } + + /** + * @return the pushCertNonceStatus + */ + public NonceStatus getNonceStatus() { + return nonceStatus; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java new file mode 100644 index 000000000..f05186cd5 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2015, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - 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. + * + * - 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. + * + * 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. + */ +package org.eclipse.jgit.transport; + +import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_PUSH_CERT; + +import java.io.EOFException; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.BaseReceivePack.ReceiveConfig; + +/** + * @author sbeller + * + */ +public class PushCertificateParser extends PushCertificate { + + private static final String VERSION = "version "; //$NON-NLS-1$ + + private static final String PUSHER = "pusher"; //$NON-NLS-1$ + + private static final String PUSHEE = "pushee"; //$NON-NLS-1$ + + private static final String NONCE = "nonce"; //$NON-NLS-1$ + + /** The individual certificate which is presented to the client */ + private String sentNonce; + + /** + * The nonce the pusher signed. This may vary from pushCertNonce See + * git-core documentation for reasons. + */ + private String receivedNonce; + + /** + * The maximum time difference which is acceptable between advertised nonce + * and received signed nonce. + */ + private int nonceSlopLimit; + + NonceGenerator nonceGenerator; + + /** + * used to build up commandlist + */ + StringBuilder commandlistBuilder; + + /** Database we write the push certificate into. */ + private Repository db; + + PushCertificateParser(Repository into, ReceiveConfig cfg) { + nonceSlopLimit = cfg.certNonceSlopLimit; + nonceGenerator = cfg.certNonceSeed != null + ? new HMACSHA1NonceGenerator(cfg.certNonceSeed) + : null; + db = into; + } + + /** + * @return if the server is configured to use signed pushes. + */ + public boolean enabled() { + return nonceGenerator != null; + } + + /** + * @return the whole string for the nonce to be included into the capability + * advertisement. + */ + public String getAdvertiseNonce() { + sentNonce = nonceGenerator.createNonce(db, + TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())); + return CAPABILITY_PUSH_CERT + "=" + sentNonce; //$NON-NLS-1$ + } + + private String parseNextLine(PacketLineIn pckIn, String startingWith) + throws IOException { + String s = pckIn.readString(); + if (!s.startsWith(startingWith)) + throw new IOException(MessageFormat.format( + JGitText.get().errorInvalidPushCert, + "expected " + startingWith)); //$NON-NLS-1$ + return s.substring(startingWith.length()); + } + + /** + * Receive a list of commands from the input encapsulated in a push + * certificate. This method doesn't deal with the first line "push-cert \NUL + * ", but assumes the first line including the capabilities + * has already been dealt with. + * + * @param pckIn + * where we take the push certificate header from. + * @param stateless + * If this server is run as a stateless server, such that it + * cannot store the sent push certificate and needs to validate + * what the client sends back. + * + * @throws IOException + */ + public void receiveHeader(PacketLineIn pckIn, boolean stateless) + throws IOException { + try { + String version = parseNextLine(pckIn, VERSION); + if (!version.equals("0.1")) { //$NON-NLS-1$ + throw new IOException(MessageFormat.format( + JGitText.get().errorInvalidPushCert, + "version not supported")); //$NON-NLS-1$ + } + pusher = parseNextLine(pckIn, PUSHER); + pushee = parseNextLine(pckIn, PUSHEE); + receivedNonce = parseNextLine(pckIn, NONCE); + // an empty line + if (pckIn.readString() != "") { //$NON-NLS-1$ + throw new IOException(MessageFormat.format( + JGitText.get().errorInvalidPushCert, + "expected empty line after header")); //$NON-NLS-1$ + } + } catch (EOFException eof) { + throw new IOException(MessageFormat.format( + JGitText.get().errorInvalidPushCert, + "broken push certificate header")); //$NON-NLS-1$ + } + nonceStatus = nonceGenerator.verify(receivedNonce, sentNonce, db, + stateless, nonceSlopLimit); + } + + /** + * Reads the gpg signature. This method assumes the line "-----BEGIN PGP + * SIGNATURE-----\n" has already been parsed and continues parsing until an + * "-----END PGP SIGNATURE-----\n" is found. + * + * @param pckIn + * where we read the signature from. + * @throws IOException + */ + public void receiveSignature(PacketLineIn pckIn) throws IOException { + try { + StringBuilder sig = new StringBuilder(); + String line = pckIn.readStringRaw(); + while (!line.equals("-----END PGP SIGNATURE-----\n")) //$NON-NLS-1$ + sig.append(line); + signature = sig.toString(); + commandList = commandlistBuilder.toString(); + } catch (EOFException eof) { + throw new IOException(MessageFormat.format( + JGitText.get().errorInvalidPushCert, + "broken push certificate signature")); //$NON-NLS-1$ + } + } + + /** + * @param rawLine + */ + public void addCommand(String rawLine) { + commandlistBuilder.append(rawLine); + } +}