View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.scm.provider.git.command.checkout;
20  
21  import java.io.IOException;
22  import java.io.OutputStream;
23  import java.io.Writer;
24  import java.nio.file.FileSystem;
25  import java.nio.file.Files;
26  import java.nio.file.Path;
27  import java.nio.file.attribute.PosixFilePermissions;
28  import java.security.GeneralSecurityException;
29  import java.security.KeyPair;
30  import java.security.PublicKey;
31  import java.util.ArrayList;
32  import java.util.List;
33  
34  import org.apache.maven.scm.provider.ScmProviderRepositoryWithHost;
35  import org.apache.maven.scm.provider.git.GitScmTestUtils;
36  import org.apache.maven.scm.repository.ScmRepository;
37  import org.apache.maven.scm.tck.command.checkout.CheckOutCommandTckTest;
38  import org.apache.sshd.common.config.keys.KeyUtils;
39  import org.apache.sshd.common.config.keys.writer.openssh.OpenSSHKeyEncryptionContext;
40  import org.apache.sshd.common.config.keys.writer.openssh.OpenSSHKeyPairResourceWriter;
41  import org.apache.sshd.git.GitLocationResolver;
42  import org.apache.sshd.git.pack.GitPackCommandFactory;
43  import org.apache.sshd.server.SshServer;
44  import org.apache.sshd.server.auth.pubkey.KeySetPublickeyAuthenticator;
45  import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
46  import org.apache.sshd.server.session.ServerSession;
47  import org.apache.sshd.util.test.CommonTestSupportUtils;
48  import org.apache.sshd.util.test.CoreTestSupportUtils;
49  import org.bouncycastle.openssl.PKCS8Generator;
50  import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
51  import org.bouncycastle.openssl.jcajce.JcaPKCS8Generator;
52  import org.bouncycastle.util.io.pem.PemObject;
53  import org.junit.Assume;
54  import org.junit.Rule;
55  import org.junit.Test;
56  import org.junit.rules.TemporaryFolder;
57  
58  /**
59   *
60   */
61  public abstract class GitSshCheckOutCommandTckTest extends CheckOutCommandTckTest {
62      protected final SshServer sshServer;
63      protected final KeyPair keyPair;
64      protected final List<PublicKey> acceptedPublicKeys;
65  
66      @Rule
67      public TemporaryFolder tmpDirectory = new TemporaryFolder();
68  
69      protected GitSshCheckOutCommandTckTest() throws GeneralSecurityException {
70          sshServer = CoreTestSupportUtils.setupTestServer(getClass());
71          keyPair = CommonTestSupportUtils.generateKeyPair(KeyUtils.RSA_ALGORITHM, 2048);
72          acceptedPublicKeys = new ArrayList<>();
73          acceptedPublicKeys.add(keyPair.getPublic());
74          PublickeyAuthenticator authenticator = new KeySetPublickeyAuthenticator("onlykey", acceptedPublicKeys);
75          sshServer.setPublickeyAuthenticator(authenticator);
76      }
77  
78      void writePrivateKeyAsPkcs8(Path file, String passphrase) throws IOException, GeneralSecurityException {
79          // encryption only optional
80          if (passphrase != null) {
81              // encryption with format outlined in https://dnaeon.github.io/openssh-private-key-binary-format/
82              OpenSSHKeyPairResourceWriter writer = new OpenSSHKeyPairResourceWriter();
83              OpenSSHKeyEncryptionContext context = new OpenSSHKeyEncryptionContext();
84              context.setCipherType("192");
85              context.setPassword(passphrase);
86              try (OutputStream output = Files.newOutputStream(file)) {
87                  writer.writePrivateKey(keyPair, "comment", context, output);
88              }
89          } else {
90              // wrap unencrypted private key as regular PKCS8 private key
91              PKCS8Generator pkcs8Generator = new JcaPKCS8Generator(keyPair.getPrivate(), null);
92              PemObject pemObject = pkcs8Generator.generate();
93  
94              try (Writer writer = Files.newBufferedWriter(file);
95                      JcaPEMWriter pw = new JcaPEMWriter(writer)) {
96                  pw.writeObject(pemObject);
97              }
98          }
99  
100         if (file.getFileSystem().supportedFileAttributeViews().contains("posix")) {
101             // must only be readable/writeable by me
102             Files.setPosixFilePermissions(file, PosixFilePermissions.fromString("rwx------"));
103         }
104     }
105 
106     protected abstract String getScmProvider();
107 
108     /** {@inheritDoc} */
109     public String getScmUrl() throws Exception {
110         return "scm:" + getScmProvider() + ":ssh://localhost:" + sshServer.getPort() + "/repository";
111     }
112 
113     public void configureCredentials(ScmRepository repository, String passphrase) throws Exception {
114         ScmProviderRepositoryWithHost providerRepository =
115                 ScmProviderRepositoryWithHost.class.cast(repository.getProviderRepository());
116         // store as file
117         Path privateKeyFile = tmpDirectory.newFile().toPath();
118         writePrivateKeyAsPkcs8(privateKeyFile, passphrase);
119         providerRepository.setPrivateKey(privateKeyFile.toString());
120         providerRepository.setPassphrase(passphrase); // may be null
121     }
122 
123     /** {@inheritDoc} */
124     public void initRepo() throws Exception {
125         GitScmTestUtils.initRepo("src/test/resources/repository/", getRepositoryRoot(), getWorkingDirectory());
126 
127         GitLocationResolver gitLocationResolver = new GitLocationResolver() {
128             @Override
129             public Path resolveRootDirectory(String command, String[] args, ServerSession session, FileSystem fs)
130                     throws IOException {
131                 return getRepositoryRoot().getParentFile().toPath();
132             }
133         };
134         sshServer.setCommandFactory(new GitPackCommandFactory(gitLocationResolver));
135         sshServer.start();
136 
137         // as checkout also already happens in setup() make sure to configure credentials here as well
138         configureCredentials(getScmRepository(), null);
139     }
140 
141     @Override
142     public void removeRepo() throws Exception {
143         sshServer.stop();
144         super.removeRepo();
145     }
146 
147     @Override
148     @Test
149     public void testCheckOutCommandTest() throws Exception {
150         configureCredentials(getScmRepository(), null);
151         super.testCheckOutCommandTest();
152     }
153 
154     @Test
155     public void testCheckOutCommandWithPassphraseTest() throws Exception {
156         // TODO: currently no easy way to pass passphrase in gitexe
157         Assume.assumeTrue(
158                 "Ignore test with passphrase for provider " + getScmProvider(), "jgit".equals(getScmProvider()));
159         configureCredentials(getScmRepository(), "mySecret");
160         super.testCheckOutCommandTest();
161     }
162 }