[MNG-2477] merge in the subset of code used
git-svn-id: https://svn.apache.org/repos/asf/maven/artifact/branches/MNG-2477@682860 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index 112a475..e59c527 100644
--- a/pom.xml
+++ b/pom.xml
@@ -85,16 +85,16 @@
<version>${wagon.version}</version>
</dependency>
<dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-openpgp</artifactId>
- <version>1.0-SNAPSHOT</version>
- </dependency>
- <dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcpg-jdk12</artifactId>
+ <version>130</version>
+ </dependency>
+ <dependency>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-file</artifactId>
<version>${wagon.version}</version>
diff --git a/src/main/java/org/apache/maven/artifact/manager/DefaultWagonManager.java b/src/main/java/org/apache/maven/artifact/manager/DefaultWagonManager.java
index ffc5152..954f859 100644
--- a/src/main/java/org/apache/maven/artifact/manager/DefaultWagonManager.java
+++ b/src/main/java/org/apache/maven/artifact/manager/DefaultWagonManager.java
@@ -33,11 +33,11 @@
import java.util.Map;
import java.util.Set;
-import org.apache.commons.openpgp.BouncyCastleKeyRing;
-import org.apache.commons.openpgp.KeyRing;
-import org.apache.commons.openpgp.OpenPgpException;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.metadata.ArtifactMetadata;
+import org.apache.maven.artifact.pgp.OpenPgpException;
+import org.apache.maven.artifact.pgp.PublicKeyRing;
+import org.apache.maven.artifact.pgp.WagonOpenPgpSignatureVerifierObserver;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.ArtifactRepositoryFactory;
import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
@@ -56,7 +56,6 @@
import org.apache.maven.wagon.proxy.ProxyInfo;
import org.apache.maven.wagon.repository.Repository;
import org.apache.maven.wagon.repository.RepositoryPermissions;
-import org.bouncycastle.openpgp.PGPException;
import org.codehaus.plexus.PlexusConstants;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.configurator.BasicComponentConfigurator;
@@ -127,7 +126,7 @@
/** @plexus.requirement */
private UpdateCheckManager updateCheckManager;
- private KeyRing keyRing = new BouncyCastleKeyRing();
+ private PublicKeyRing keyRing = new PublicKeyRing();
// TODO: this leaks the component in the public api - it is never released back to the container
public Wagon getWagon( Repository repository )
@@ -1356,9 +1355,8 @@
}
public void registerPublicKeyRing( InputStream inputStream )
- throws IOException, PGPException
+ throws IOException, OpenPgpException
{
- // TODO: remove PGPException
this.keyRing.addPublicKeyRing( inputStream );
// TODO: debug logging of keys
diff --git a/src/main/java/org/apache/maven/artifact/manager/WagonManager.java b/src/main/java/org/apache/maven/artifact/manager/WagonManager.java
index 7877916..e01cff2 100644
--- a/src/main/java/org/apache/maven/artifact/manager/WagonManager.java
+++ b/src/main/java/org/apache/maven/artifact/manager/WagonManager.java
@@ -27,6 +27,7 @@
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.metadata.ArtifactMetadata;
+import org.apache.maven.artifact.pgp.OpenPgpException;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.wagon.ResourceDoesNotExistException;
import org.apache.maven.wagon.TransferFailedException;
@@ -37,7 +38,6 @@
import org.apache.maven.wagon.proxy.ProxyInfo;
import org.apache.maven.wagon.repository.Repository;
import org.apache.maven.wagon.repository.RepositoryPermissions;
-import org.bouncycastle.openpgp.PGPException;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.util.xml.Xpp3Dom;
@@ -176,5 +176,5 @@
ArtifactRepository getMirrorRepository( ArtifactRepository repository );
void registerPublicKeyRing( InputStream fileInputStream )
- throws IOException, PGPException;
+ throws IOException, OpenPgpException;
}
diff --git a/src/main/java/org/apache/maven/artifact/pgp/OpenPgpException.java b/src/main/java/org/apache/maven/artifact/pgp/OpenPgpException.java
new file mode 100644
index 0000000..bcfc587
--- /dev/null
+++ b/src/main/java/org/apache/maven/artifact/pgp/OpenPgpException.java
@@ -0,0 +1,45 @@
+package org.apache.maven.artifact.pgp;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * An exception occurring during the use of the OpenPGP library.
+ *
+ * @author <a href="mailto:brett@apache.org">Brett Porter</a>
+ */
+public class OpenPgpException
+ extends Exception
+{
+ private Throwable cause;
+
+ public OpenPgpException( String message )
+ {
+ super( message );
+ }
+
+ public OpenPgpException( String message, Throwable cause )
+ {
+ super( message );
+ this.cause = cause;
+ }
+
+ public Throwable getCause()
+ {
+ return cause;
+ }
+}
diff --git a/src/main/java/org/apache/maven/artifact/pgp/PublicKeyRing.java b/src/main/java/org/apache/maven/artifact/pgp/PublicKeyRing.java
new file mode 100644
index 0000000..3e96e8c
--- /dev/null
+++ b/src/main/java/org/apache/maven/artifact/pgp/PublicKeyRing.java
@@ -0,0 +1,75 @@
+package org.apache.maven.artifact.pgp;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bouncycastle.openpgp.PGPObjectFactory;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.bouncycastle.openpgp.PGPUtil;
+
+/**
+ * Bouncy Castle implementation of the OpenPGP key ring.
+ *
+ * @author <a href="mailto:brett@apache.org">Brett Porter</a>
+ */
+public class PublicKeyRing
+{
+ private final Map<Long,PGPPublicKey> keyRingMap = new HashMap<Long,PGPPublicKey>();
+
+ private static final long MASK = 0xFFFFFFFFL;
+
+ public PublicKeyRing()
+ {
+ }
+
+ public void addPublicKeyRing( InputStream publicKeyRingStream )
+ throws IOException, OpenPgpException
+ {
+ PGPObjectFactory pgpFact = new PGPObjectFactory( PGPUtil.getDecoderStream( publicKeyRingStream ) );
+ Object obj;
+
+ while ( ( obj = pgpFact.nextObject() ) != null )
+ {
+ if ( !( obj instanceof PGPPublicKeyRing ) )
+ {
+ throw new OpenPgpException( "Invalid key ring (" + obj.getClass().getName()
+ + " found where PGPPublicKeyRing expected)" );
+ }
+
+ PGPPublicKeyRing keyRing = (PGPPublicKeyRing) obj;
+ Long key = new Long( keyRing.getPublicKey().getKeyID() & MASK );
+
+ this.keyRingMap.put( key, keyRing.getPublicKey() );
+ }
+ }
+
+ PGPPublicKey getPublicKey( String keyId )
+ {
+ return (PGPPublicKey) keyRingMap.get( Long.valueOf( keyId, 16 ) );
+ }
+
+ PGPPublicKey getPublicKey( long keyId )
+ {
+ return (PGPPublicKey) keyRingMap.get( new Long( keyId & MASK ) );
+ }
+}
diff --git a/src/main/java/org/apache/maven/artifact/pgp/SecretKeyRing.java b/src/main/java/org/apache/maven/artifact/pgp/SecretKeyRing.java
new file mode 100644
index 0000000..600dc5e
--- /dev/null
+++ b/src/main/java/org/apache/maven/artifact/pgp/SecretKeyRing.java
@@ -0,0 +1,84 @@
+package org.apache.maven.artifact.pgp;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bouncycastle.openpgp.PGPObjectFactory;
+import org.bouncycastle.openpgp.PGPSecretKey;
+import org.bouncycastle.openpgp.PGPSecretKeyRing;
+import org.bouncycastle.openpgp.PGPUtil;
+
+/**
+ * Bouncy Castle implementation of the OpenPGP key ring.
+ *
+ * @author <a href="mailto:brett@apache.org">Brett Porter</a>
+ * @todo password is not secure
+ */
+public class SecretKeyRing
+{
+ private final Map<Long,PGPSecretKey> pgpSec = new HashMap<Long, PGPSecretKey>();
+
+ private char[] password;
+
+ private static final long MASK = 0xFFFFFFFFL;
+
+ public SecretKeyRing()
+ {
+ }
+
+ public void addSecretKeyRing( InputStream secretKeyRingStream, char[] password )
+ throws IOException, OpenPgpException
+ {
+ PGPObjectFactory pgpFact = new PGPObjectFactory( PGPUtil.getDecoderStream( secretKeyRingStream ) );
+ Object obj;
+
+ while ( ( obj = pgpFact.nextObject() ) != null )
+ {
+ if ( !( obj instanceof PGPSecretKeyRing ) )
+ {
+ throw new OpenPgpException( obj.getClass().getName() + " found where PGPSecretKeyRing expected" );
+ }
+
+ PGPSecretKeyRing pgpSecret = (PGPSecretKeyRing) obj;
+ Long key = new Long( pgpSecret.getSecretKey().getKeyID() & MASK );
+
+ pgpSec.put( key, pgpSecret.getSecretKey() );
+ }
+
+ this.password = password;
+ }
+
+ public char[] getPassword()
+ {
+ return password;
+ }
+
+ public PGPSecretKey getSecretKey( String keyId )
+ {
+ return (PGPSecretKey) pgpSec.get( Long.valueOf( keyId, 16 ) );
+ }
+
+ public PGPSecretKey getSecretKey( long keyId )
+ {
+ return (PGPSecretKey) pgpSec.get( new Long( keyId & MASK ) );
+ }
+}
diff --git a/src/main/java/org/apache/maven/artifact/pgp/SignatureStatus.java b/src/main/java/org/apache/maven/artifact/pgp/SignatureStatus.java
new file mode 100644
index 0000000..f0ac9fd
--- /dev/null
+++ b/src/main/java/org/apache/maven/artifact/pgp/SignatureStatus.java
@@ -0,0 +1,75 @@
+package org.apache.maven.artifact.pgp;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Enumerated type indicating the status of data that was signed.
+ * <p/>
+ * Values:
+ * <ul>
+ * <li><code>VALID_TRUSTED</code></li>
+ * <li><code>VALID_UNTRUSTED</code></li>
+ * <li><code>INVALID</code></li>
+ * </ul>
+ *
+ * @author <a href="mailto:brett@apache.org">Brett Porter</a>
+ * @todo incorporate levels of trust
+ */
+public class SignatureStatus
+{
+ /**
+ * Status that indicates the signature is valid, and from a trusted source.
+ */
+ public static SignatureStatus VALID_TRUSTED = new SignatureStatus( true, true );
+
+ /**
+ * Status that indicates the signature is valid, but from an unknown or untrusted source.
+ */
+ public static SignatureStatus VALID_UNTRUSTED = new SignatureStatus( true, false );
+
+ /**
+ * Status that indicates the signature is invalid.
+ */
+ public static SignatureStatus INVALID = new SignatureStatus( false, false );
+
+ /**
+ * Whether the signature is valid.
+ */
+ private final boolean valid;
+
+ /**
+ * Whether the signature is trusted.
+ */
+ private final boolean trusted;
+
+ private SignatureStatus( boolean valid, boolean trusted )
+ {
+ this.valid = valid;
+ this.trusted = trusted;
+ }
+
+ public boolean isValid()
+ {
+ return valid;
+ }
+
+ public boolean isTrusted()
+ {
+ return trusted;
+ }
+}
diff --git a/src/main/java/org/apache/maven/artifact/pgp/SignatureVerifier.java b/src/main/java/org/apache/maven/artifact/pgp/SignatureVerifier.java
new file mode 100644
index 0000000..cd4c298
--- /dev/null
+++ b/src/main/java/org/apache/maven/artifact/pgp/SignatureVerifier.java
@@ -0,0 +1,52 @@
+package org.apache.maven.artifact.pgp;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Verify signatures using the Bouncy Castle OpenPGP provider.
+ *
+ * @author <a href="mailto:brett@apache.org">Brett Porter</a>
+ */
+public class SignatureVerifier
+{
+ private static final int BUFFER_SIZE = 1024;
+
+ public SignatureStatus verifyDetachedSignature( InputStream data, InputStream signature, PublicKeyRing keyRing )
+ throws OpenPgpException, UnknownKeyException, IOException
+ {
+ StreamingSignatureVerifier verifier = new StreamingSignatureVerifier( signature, keyRing );
+
+ byte[] buf = new byte[BUFFER_SIZE];
+
+ int len;
+ do
+ {
+ len = data.read( buf );
+ if ( len > 0 )
+ {
+ verifier.update( buf, 0, len );
+ }
+ }
+ while ( len >= 0 );
+
+ return verifier.finish();
+ }
+}
diff --git a/src/main/java/org/apache/maven/artifact/pgp/StreamingSignatureVerifier.java b/src/main/java/org/apache/maven/artifact/pgp/StreamingSignatureVerifier.java
new file mode 100644
index 0000000..02e87c8
--- /dev/null
+++ b/src/main/java/org/apache/maven/artifact/pgp/StreamingSignatureVerifier.java
@@ -0,0 +1,165 @@
+package org.apache.maven.artifact.pgp;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.NoSuchProviderException;
+import java.security.Security;
+import java.security.SignatureException;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openpgp.PGPCompressedData;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPObjectFactory;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.PGPSignatureList;
+import org.bouncycastle.openpgp.PGPUtil;
+
+/**
+ * Bouncy Castle implementation of the OpenPGP signer.
+ *
+ * @author <a href="mailto:brett@apache.org">Brett Porter</a>
+ */
+public class StreamingSignatureVerifier
+{
+ private PGPSignature sig;
+
+ public StreamingSignatureVerifier( InputStream signature, PublicKeyRing keyRing )
+ throws OpenPgpException, IOException
+ {
+ init( signature, keyRing );
+ }
+
+ private void init( InputStream signature, PublicKeyRing keyRing )
+ throws OpenPgpException, IOException
+ {
+ // TODO: better location for this?
+ Security.addProvider( new BouncyCastleProvider() );
+
+ try
+ {
+ signature = PGPUtil.getDecoderStream( signature );
+
+ PGPPublicKey key = null;
+ while ( key == null && signature.available() > 0 )
+ {
+ PGPObjectFactory pgpFact = new PGPObjectFactory( signature );
+
+ PGPSignatureList p3;
+
+ Object o = pgpFact.nextObject();
+ if ( o == null )
+ {
+ break;
+ }
+
+ if ( o instanceof PGPCompressedData )
+ {
+ PGPCompressedData c1 = (PGPCompressedData) o;
+
+ pgpFact = new PGPObjectFactory( c1.getDataStream() );
+
+ p3 = (PGPSignatureList) pgpFact.nextObject();
+ }
+ else
+ {
+ p3 = (PGPSignatureList) o;
+ }
+
+ for ( int i = 0; i < p3.size(); i++ )
+ {
+ sig = p3.get( i );
+ key = keyRing.getPublicKey( sig.getKeyID() );
+ if ( key != null )
+ {
+ break;
+ }
+ else
+ {
+ // TODO: log them all
+ }
+ }
+
+ }
+
+ if ( key == null )
+ {
+ throw new UnknownKeyException( "Unable to find key with key ID '"
+ + Long.toHexString( sig.getKeyID() ).toUpperCase() + "' in public key ring" );
+ }
+
+ sig.initVerify( key, "BC" );
+ }
+ catch ( NoSuchProviderException e )
+ {
+ throw new OpenPgpException(
+ "Unable to find the correct provider for PGP - check that the Bouncy Castle provider is correctly installed",
+ e );
+ }
+ catch ( PGPException e )
+ {
+ throw new OpenPgpException( "Error calculating detached signature: " + e.getMessage(), e );
+ }
+ }
+
+ public void update( byte[] buf )
+ throws OpenPgpException
+ {
+ update( buf, 0, buf.length );
+ }
+
+ public void update( byte[] buf, int offset, int length )
+ throws OpenPgpException
+ {
+ try
+ {
+ sig.update( buf, offset, length );
+ }
+ catch ( SignatureException e )
+ {
+ throw new OpenPgpException( "Error calculating detached signature: " + e.getMessage(), e );
+ }
+ }
+
+ public SignatureStatus finish()
+ throws OpenPgpException, IOException
+ {
+ try
+ {
+ if ( sig.verify() )
+ {
+ // TODO: how do we assess trust?
+ return SignatureStatus.VALID_UNTRUSTED;
+ }
+ else
+ {
+ return SignatureStatus.INVALID;
+ }
+ }
+ catch ( PGPException e )
+ {
+ throw new OpenPgpException( "Error calculating detached signature: " + e.getMessage(), e );
+ }
+ catch ( SignatureException e )
+ {
+ throw new OpenPgpException( "Error calculating detached signature: " + e.getMessage(), e );
+ }
+ }
+}
diff --git a/src/main/java/org/apache/maven/artifact/pgp/StreamingSigner.java b/src/main/java/org/apache/maven/artifact/pgp/StreamingSigner.java
new file mode 100644
index 0000000..61459c5
--- /dev/null
+++ b/src/main/java/org/apache/maven/artifact/pgp/StreamingSigner.java
@@ -0,0 +1,146 @@
+package org.apache.maven.artifact.pgp;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.bouncycastle.bcpg.ArmoredOutputStream;
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPSecretKey;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.PGPSignatureGenerator;
+import org.bouncycastle.openpgp.PGPUtil;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Security;
+import java.security.SignatureException;
+
+/**
+ * Bouncy Castle implementation of the OpenPGP signer.
+ *
+ * @author <a href="mailto:brett@apache.org">Brett Porter</a>
+ */
+public class StreamingSigner
+{
+ private static final String PROVIDER = "BC";
+
+ private PGPSignatureGenerator sGen;
+
+ private final ByteArrayOutputStream signatureBytes;
+
+ private BCPGOutputStream bOut;
+
+ public StreamingSigner( String keyId, SecretKeyRing keyRing, boolean asciiArmor )
+ throws OpenPgpException
+ {
+ signatureBytes = new ByteArrayOutputStream();
+ init( asciiArmor, signatureBytes, keyRing, keyId );
+ }
+
+ public StreamingSigner( OutputStream signature, String keyId, SecretKeyRing keyRing, boolean asciiArmor )
+ throws OpenPgpException
+ {
+ signatureBytes = null;
+ init( asciiArmor, signature, keyRing, keyId );
+ }
+
+ private void init( boolean asciiArmor, OutputStream signature, SecretKeyRing keyRing, String keyId )
+ throws OpenPgpException
+ {
+ // TODO: better location for this?
+ Security.addProvider( new BouncyCastleProvider() );
+
+ OutputStream out;
+ if ( asciiArmor )
+ {
+ out = new ArmoredOutputStream( signature );
+ }
+ else
+ {
+ out = signature;
+ }
+ bOut = new BCPGOutputStream( out );
+
+ try
+ {
+ PGPSecretKey pgpSec = keyRing.getSecretKey( keyId );
+ PGPPrivateKey pgpPrivKey = pgpSec.extractPrivateKey( keyRing.getPassword(), PROVIDER );
+ sGen = new PGPSignatureGenerator( pgpSec.getPublicKey().getAlgorithm(), PGPUtil.SHA1, PROVIDER );
+ sGen.initSign( PGPSignature.BINARY_DOCUMENT, pgpPrivKey );
+ }
+ catch ( NoSuchAlgorithmException e )
+ {
+ throw new OpenPgpException(
+ "Unable to find the correct algorithm for PGP - check that the Bouncy Castle provider is correctly installed",
+ e );
+ }
+ catch ( NoSuchProviderException e )
+ {
+ throw new OpenPgpException(
+ "Unable to find the correct provider for PGP - check that the Bouncy Castle provider is correctly installed",
+ e );
+ }
+ catch ( PGPException e )
+ {
+ throw new OpenPgpException( "Error calculating detached signature: " + e.getMessage(), e );
+ }
+ }
+
+ public void update( byte[] buf )
+ throws OpenPgpException
+ {
+ update( buf, 0, buf.length );
+ }
+
+ public void update( byte[] buf, int offset, int length )
+ throws OpenPgpException
+ {
+ try
+ {
+ sGen.update( buf, offset, length );
+ }
+ catch ( SignatureException e )
+ {
+ throw new OpenPgpException( "Error calculating detached signature: " + e.getMessage(), e );
+ }
+ }
+
+ public byte[] finish()
+ throws OpenPgpException, IOException
+ {
+ try
+ {
+ sGen.generate().encode( bOut );
+ }
+ catch ( PGPException e )
+ {
+ throw new OpenPgpException( "Error calculating detached signature: " + e.getMessage(), e );
+ }
+ catch ( SignatureException e )
+ {
+ throw new OpenPgpException( "Error calculating detached signature: " + e.getMessage(), e );
+ }
+ bOut.close();
+ return signatureBytes != null ? signatureBytes.toByteArray() : null;
+ }
+}
diff --git a/src/main/java/org/apache/maven/artifact/pgp/UnknownKeyException.java b/src/main/java/org/apache/maven/artifact/pgp/UnknownKeyException.java
new file mode 100644
index 0000000..664d4fc
--- /dev/null
+++ b/src/main/java/org/apache/maven/artifact/pgp/UnknownKeyException.java
@@ -0,0 +1,37 @@
+package org.apache.maven.artifact.pgp;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * An exception occurring during the verification of a signature, when the key is not found in the keyring.
+ *
+ * @author <a href="mailto:brett@apache.org">Brett Porter</a>
+ */
+public class UnknownKeyException
+ extends OpenPgpException
+{
+ public UnknownKeyException( String message )
+ {
+ super( message );
+ }
+
+ public UnknownKeyException( String message, Throwable cause )
+ {
+ super( message, cause );
+ }
+}
diff --git a/src/main/java/org/apache/maven/artifact/manager/WagonOpenPgpSignatureVerifierObserver.java b/src/main/java/org/apache/maven/artifact/pgp/WagonOpenPgpSignatureVerifierObserver.java
similarity index 81%
rename from src/main/java/org/apache/maven/artifact/manager/WagonOpenPgpSignatureVerifierObserver.java
rename to src/main/java/org/apache/maven/artifact/pgp/WagonOpenPgpSignatureVerifierObserver.java
index 3db953a..042e225 100644
--- a/src/main/java/org/apache/maven/artifact/manager/WagonOpenPgpSignatureVerifierObserver.java
+++ b/src/main/java/org/apache/maven/artifact/pgp/WagonOpenPgpSignatureVerifierObserver.java
@@ -1,4 +1,4 @@
-package org.apache.maven.artifact.manager;
+package org.apache.maven.artifact.pgp;
/*
* Licensed to the Apache Software Foundation (ASF) under one
@@ -19,11 +19,6 @@
* under the License.
*/
-import org.apache.commons.openpgp.BouncyCastleOpenPgpStreamingSignatureVerifier;
-import org.apache.commons.openpgp.KeyRing;
-import org.apache.commons.openpgp.OpenPgpException;
-import org.apache.commons.openpgp.OpenPgpStreamingSignatureVerifier;
-import org.apache.commons.openpgp.SignatureStatus;
import org.apache.maven.wagon.events.TransferEvent;
import org.apache.maven.wagon.observers.AbstractTransferListener;
@@ -38,16 +33,16 @@
public class WagonOpenPgpSignatureVerifierObserver
extends AbstractTransferListener
{
- private final OpenPgpStreamingSignatureVerifier verifier;
+ private final StreamingSignatureVerifier verifier;
private SignatureStatus status;
private Exception failure;
- public WagonOpenPgpSignatureVerifierObserver( InputStream signatureInputStream, KeyRing keyRing )
+ public WagonOpenPgpSignatureVerifierObserver( InputStream signatureInputStream, PublicKeyRing keyRing )
throws OpenPgpException, IOException
{
- verifier = new BouncyCastleOpenPgpStreamingSignatureVerifier( signatureInputStream, keyRing );
+ verifier = new StreamingSignatureVerifier( signatureInputStream, keyRing );
}
public void transferInitiated( TransferEvent transferEvent )
diff --git a/src/main/java/org/apache/maven/artifact/manager/WagonOpenPgpSignerObserver.java b/src/main/java/org/apache/maven/artifact/pgp/WagonOpenPgpSignerObserver.java
similarity index 82%
rename from src/main/java/org/apache/maven/artifact/manager/WagonOpenPgpSignerObserver.java
rename to src/main/java/org/apache/maven/artifact/pgp/WagonOpenPgpSignerObserver.java
index d8320e6..f5f9bf2 100644
--- a/src/main/java/org/apache/maven/artifact/manager/WagonOpenPgpSignerObserver.java
+++ b/src/main/java/org/apache/maven/artifact/pgp/WagonOpenPgpSignerObserver.java
@@ -1,4 +1,4 @@
-package org.apache.maven.artifact.manager;
+package org.apache.maven.artifact.pgp;
/*
* Licensed to the Apache Software Foundation (ASF) under one
@@ -19,10 +19,6 @@
* under the License.
*/
-import org.apache.commons.openpgp.BouncyCastleOpenPgpStreamingSigner;
-import org.apache.commons.openpgp.KeyRing;
-import org.apache.commons.openpgp.OpenPgpException;
-import org.apache.commons.openpgp.OpenPgpStreamingSigner;
import org.apache.maven.wagon.events.TransferEvent;
import org.apache.maven.wagon.observers.AbstractTransferListener;
@@ -36,16 +32,16 @@
public class WagonOpenPgpSignerObserver
extends AbstractTransferListener
{
- private final OpenPgpStreamingSigner signer;
+ private final StreamingSigner signer;
private byte[] actualSignature;
private Exception failure;
- public WagonOpenPgpSignerObserver( String keyId, KeyRing keyRing, boolean asciiArmored )
+ public WagonOpenPgpSignerObserver( String keyId, SecretKeyRing keyRing, boolean asciiArmored )
throws OpenPgpException
{
- signer = new BouncyCastleOpenPgpStreamingSigner( keyId, keyRing, asciiArmored );
+ signer = new StreamingSigner( keyId, keyRing, asciiArmored );
}
public void transferInitiated( TransferEvent transferEvent )
diff --git a/src/test/java/org/apache/maven/artifact/pgp/PublicKeyRingTest.java b/src/test/java/org/apache/maven/artifact/pgp/PublicKeyRingTest.java
new file mode 100644
index 0000000..c641851
--- /dev/null
+++ b/src/test/java/org/apache/maven/artifact/pgp/PublicKeyRingTest.java
@@ -0,0 +1,53 @@
+package org.apache.maven.artifact.pgp;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+/**
+ * Test the open pgp key ring.
+ *
+ * @author <a href="mailto:brett@apache.org">Brett Porter</a>
+ */
+public class PublicKeyRingTest
+ extends TestCase
+{
+ private String[] pubKeyId = { "A7D16BD4" };
+
+ private PublicKeyRing keyRing;
+
+ protected void setUp()
+ throws Exception
+ {
+ super.setUp();
+
+ keyRing = new PublicKeyRing();
+ keyRing.addPublicKeyRing( getClass().getResourceAsStream( "/pubring.gpg" ) );
+ }
+
+ public void testPublicKeys()
+ throws OpenPgpException, IOException
+ {
+ for ( int i = 0; i < pubKeyId.length; i++ )
+ {
+ assertNotNull( "Unable to find key " + pubKeyId[i], keyRing.getPublicKey( pubKeyId[i] ) );
+ }
+ }
+}
diff --git a/src/test/java/org/apache/maven/artifact/pgp/SecretKeyRingTest.java b/src/test/java/org/apache/maven/artifact/pgp/SecretKeyRingTest.java
new file mode 100644
index 0000000..34f3838
--- /dev/null
+++ b/src/test/java/org/apache/maven/artifact/pgp/SecretKeyRingTest.java
@@ -0,0 +1,55 @@
+package org.apache.maven.artifact.pgp;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+/**
+ * Test the open pgp key ring.
+ *
+ * @author <a href="mailto:brett@apache.org">Brett Porter</a>
+ */
+public class SecretKeyRingTest
+ extends TestCase
+{
+ private String[] secKeyId = { "A7D16BD4" };
+
+ private SecretKeyRing keyRing;
+
+ private static final String PASSWORD = "cop";
+
+ protected void setUp()
+ throws Exception
+ {
+ super.setUp();
+
+ keyRing = new SecretKeyRing();
+ keyRing.addSecretKeyRing( getClass().getResourceAsStream( "/secring.gpg" ), PASSWORD.toCharArray() );
+ }
+
+ public void testSecretKeys()
+ throws OpenPgpException, IOException
+ {
+ for ( int i = 0; i < secKeyId.length; i++ )
+ {
+ assertNotNull( "Unable to find key " + secKeyId[i], keyRing.getSecretKey( secKeyId[i] ) );
+ }
+ }
+}
diff --git a/src/test/java/org/apache/maven/artifact/pgp/StreamingSignerTest.java b/src/test/java/org/apache/maven/artifact/pgp/StreamingSignerTest.java
new file mode 100644
index 0000000..379140d
--- /dev/null
+++ b/src/test/java/org/apache/maven/artifact/pgp/StreamingSignerTest.java
@@ -0,0 +1,192 @@
+package org.apache.maven.artifact.pgp;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import junit.framework.TestCase;
+
+/**
+ * Test the open pgp signer.
+ *
+ * @author <a href="mailto:brett@apache.org">Brett Porter</a>
+ * @todo test text input as well as binary - apparently it fails cross platform
+ */
+public class StreamingSignerTest
+ extends TestCase
+{
+ private static final String FILE = "/test-input.txt";
+
+ private String keyId = "A7D16BD4";
+
+ private SecretKeyRing keyRing;
+
+ private static final String PASSWORD = "cop";
+
+ private PublicKeyRing publicKeyRing;
+
+ protected void setUp()
+ throws Exception
+ {
+ super.setUp();
+
+ keyRing = new SecretKeyRing();
+ keyRing.addSecretKeyRing( getClass().getResourceAsStream( "/secring.gpg" ), PASSWORD.toCharArray() );
+
+ publicKeyRing = new PublicKeyRing();
+ publicKeyRing.addPublicKeyRing( getClass().getResourceAsStream( "/pubring.gpg" ) );
+ }
+
+ public void testSignDataDetachedBinary()
+ throws OpenPgpException, IOException
+ {
+ StreamingSigner signer = new StreamingSigner( keyId, keyRing, false );
+
+ InputStream in = getClass().getResourceAsStream( FILE );
+ byte[] buf = new byte[8192];
+ int len;
+ try
+ {
+ do
+ {
+ len = in.read( buf, 0, 8192 );
+ if ( len > 0 )
+ {
+ signer.update( buf, 0, len );
+ }
+ }
+ while ( len >= 0 );
+ }
+ finally
+ {
+ in.close();
+ }
+
+ byte[] signature = signer.finish();
+
+ SignatureVerifier verifier = new SignatureVerifier();
+
+ SignatureStatus status =
+ verifier.verifyDetachedSignature( getClass().getResourceAsStream( FILE ),
+ new ByteArrayInputStream( signature ), publicKeyRing );
+ assertNotNull( "check we got a status", status );
+ assertTrue( "check it was successful", status.isValid() );
+ }
+
+ public void testVerifySignatureDetachedBinaryGpg()
+ throws IOException, OpenPgpException
+ {
+ InputStream signature = getClass().getResourceAsStream( "/test-input.txt.sig" );
+ StreamingSignatureVerifier verifier = new StreamingSignatureVerifier( signature, publicKeyRing );
+
+ InputStream in = getClass().getResourceAsStream( FILE );
+ byte[] buf = new byte[8192];
+ int len;
+ try
+ {
+ do
+ {
+ len = in.read( buf, 0, 8192 );
+ if ( len > 0 )
+ {
+ verifier.update( buf, 0, len );
+ }
+ }
+ while ( len >= 0 );
+ }
+ finally
+ {
+ in.close();
+ }
+
+ SignatureStatus status = verifier.finish();
+
+ assertNotNull( "check we got a status", status );
+ assertTrue( "check it was successful", status.isValid() );
+ }
+
+ public void testSignDataDetachedAscii()
+ throws OpenPgpException, IOException
+ {
+ StreamingSigner signer = new StreamingSigner( keyId, keyRing, true );
+
+ InputStream in = getClass().getResourceAsStream( FILE );
+ byte[] buf = new byte[8192];
+ int len;
+ try
+ {
+ do
+ {
+ len = in.read( buf, 0, 8192 );
+ if ( len > 0 )
+ {
+ signer.update( buf, 0, len );
+ }
+ }
+ while ( len >= 0 );
+ }
+ finally
+ {
+ in.close();
+ }
+
+ byte[] signature = signer.finish();
+
+ SignatureVerifier verifier = new SignatureVerifier();
+
+ SignatureStatus status =
+ verifier.verifyDetachedSignature( getClass().getResourceAsStream( FILE ),
+ new ByteArrayInputStream( signature ), publicKeyRing );
+ assertNotNull( "check we got a status", status );
+ assertTrue( "check it was successful", status.isValid() );
+ }
+
+ public void testVerifySignatureDetachedAscii()
+ throws IOException, OpenPgpException
+ {
+ InputStream signature = getClass().getResourceAsStream( "/test-input.txt.asc" );
+ StreamingSignatureVerifier verifier = new StreamingSignatureVerifier( signature, publicKeyRing );
+
+ InputStream in = getClass().getResourceAsStream( FILE );
+ byte[] buf = new byte[8192];
+ int len;
+ try
+ {
+ do
+ {
+ len = in.read( buf, 0, 8192 );
+ if ( len > 0 )
+ {
+ verifier.update( buf, 0, len );
+ }
+ }
+ while ( len >= 0 );
+ }
+ finally
+ {
+ in.close();
+ }
+
+ SignatureStatus status = verifier.finish();
+
+ assertNotNull( "check we got a status", status );
+ assertTrue( "check it was successful", status.isValid() );
+ }
+}
diff --git a/src/test/java/org/apache/maven/artifact/manager/WagonOpenPgpObserverTest.java b/src/test/java/org/apache/maven/artifact/pgp/WagonOpenPgpObserverTest.java
similarity index 83%
rename from src/test/java/org/apache/maven/artifact/manager/WagonOpenPgpObserverTest.java
rename to src/test/java/org/apache/maven/artifact/pgp/WagonOpenPgpObserverTest.java
index 3505f3a..3f6b7a3 100644
--- a/src/test/java/org/apache/maven/artifact/manager/WagonOpenPgpObserverTest.java
+++ b/src/test/java/org/apache/maven/artifact/pgp/WagonOpenPgpObserverTest.java
@@ -1,4 +1,4 @@
-package org.apache.maven.artifact.manager;
+package org.apache.maven.artifact.pgp;
/*
* Licensed to the Apache Software Foundation (ASF) under one
@@ -19,12 +19,8 @@
* under the License.
*/
-import org.apache.commons.openpgp.BouncyCastleKeyRing;
-import org.apache.commons.openpgp.BouncyCastleOpenPgpSignatureVerifier;
-import org.apache.commons.openpgp.KeyRing;
-import org.apache.commons.openpgp.OpenPgpException;
-import org.apache.commons.openpgp.OpenPgpSignatureVerifier;
-import org.apache.commons.openpgp.SignatureStatus;
+import org.apache.maven.artifact.pgp.WagonOpenPgpSignatureVerifierObserver;
+import org.apache.maven.artifact.pgp.WagonOpenPgpSignerObserver;
import org.apache.maven.wagon.ConnectionException;
import org.apache.maven.wagon.ResourceDoesNotExistException;
import org.apache.maven.wagon.TransferFailedException;
@@ -41,7 +37,7 @@
/**
* Test the wagon observer for open pgp signatures.
- *
+ *
* @author <a href="mailto:brett@apache.org">Brett Porter</a>
*/
public class WagonOpenPgpObserverTest
@@ -49,22 +45,23 @@
{
private String keyId = "A7D16BD4";
- private static final String PASSWORD = "cop";
-
- private KeyRing keyRing;
+ private PublicKeyRing keyRing;
protected void setUp()
throws Exception
{
super.setUp();
- keyRing = new BouncyCastleKeyRing( getClass().getResourceAsStream( "/secring.gpg" ),
- getClass().getResourceAsStream( "/pubring.gpg" ), PASSWORD.toCharArray() );
+ keyRing = new PublicKeyRing();
+ keyRing.addPublicKeyRing( getClass().getResourceAsStream( "/pubring.gpg" ) );
}
public void testSign()
throws Exception
{
+ SecretKeyRing keyRing = new SecretKeyRing();
+ keyRing.addSecretKeyRing( getClass().getResourceAsStream( "/secring.gpg" ), "cop".toCharArray() );
+
WagonOpenPgpSignerObserver observer = new WagonOpenPgpSignerObserver( keyId, keyRing, false );
Wagon wagon = (Wagon) lookup( Wagon.ROLE, "file" );
@@ -88,10 +85,10 @@
wagon.disconnect();
// check signature
- OpenPgpSignatureVerifier verifier = new BouncyCastleOpenPgpSignatureVerifier();
+ SignatureVerifier verifier = new SignatureVerifier();
SignatureStatus status =
verifier.verifyDetachedSignature( getClass().getResourceAsStream( "/test-input.txt" ),
- new ByteArrayInputStream( signature ), keyRing );
+ new ByteArrayInputStream( signature ), this.keyRing );
assertNotNull( "check we got a status", status );
assertTrue( "check it was successful", status.isValid() );
@@ -101,7 +98,7 @@
throws Exception
{
verifySignature( "/test-input.txt.sig" );
-
+
verifySignature( "/test-input.txt.asc" );
}