@ -1,5 +1,5 @@
/ *
/ *
* Copyright ( C ) 2018 , Salesforce . and others
* Copyright ( C ) 2018 , 2020 Salesforce and others
*
*
* This program and the accompanying materials are made available under the
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v . 1 . 0 which is available at
* terms of the Eclipse Distribution License v . 1 . 0 which is available at
@ -25,7 +25,9 @@ import java.nio.file.Paths;
import java.security.NoSuchAlgorithmException ;
import java.security.NoSuchAlgorithmException ;
import java.security.NoSuchProviderException ;
import java.security.NoSuchProviderException ;
import java.text.MessageFormat ;
import java.text.MessageFormat ;
import java.util.ArrayList ;
import java.util.Iterator ;
import java.util.Iterator ;
import java.util.List ;
import java.util.Locale ;
import java.util.Locale ;
import java.util.stream.Collectors ;
import java.util.stream.Collectors ;
import java.util.stream.Stream ;
import java.util.stream.Stream ;
@ -76,6 +78,13 @@ 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
private static final Logger log = LoggerFactory
. getLogger ( BouncyCastleGpgKeyLocator . class ) ;
. getLogger ( BouncyCastleGpgKeyLocator . class ) ;
@ -433,22 +442,46 @@ public class BouncyCastleGpgKeyLocator {
PGPDigestCalculatorProvider calculatorProvider = new JcaPGPDigestCalculatorProviderBuilder ( )
PGPDigestCalculatorProvider calculatorProvider = new JcaPGPDigestCalculatorProviderBuilder ( )
. build ( ) ;
. build ( ) ;
PBEProtectionRemoverFactory passphraseProvider = new JcePBEProtectionRemoverFactory (
passphrasePrompt . getPassphrase ( publicKey . getFingerprint ( ) ,
userKeyboxPath ) ) ;
try ( Stream < Path > keyFiles = Files . walk ( USER_SECRET_KEY_DIR ) ) {
try ( Stream < Path > keyFiles = Files . walk ( USER_SECRET_KEY_DIR ) ) {
for ( Path keyFile : keyFiles . filter ( Files : : isRegularFile )
List < Path > allPaths = keyFiles . filter ( Files : : isRegularFile )
. collect ( Collectors . toList ( ) ) ) {
. collect ( Collectors . toCollection ( ArrayList : : new ) ) ;
PGPSecretKey secretKey = attemptParseSecretKey ( keyFile ,
if ( allPaths . isEmpty ( ) ) {
calculatorProvider , passphraseProvider , publicKey ) ;
return null ;
if ( secretKey ! = null ) {
}
if ( ! secretKey . isSigningKey ( ) ) {
PBEProtectionRemoverFactory passphraseProvider = p - > {
throw new PGPException ( MessageFormat . format (
throw new EncryptedPgpKeyException ( ) ;
BCText . get ( ) . gpgNotASigningKey , signingKey ) ) ;
} ;
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.
}
}
return new BouncyCastleGpgKey ( secretKey , userKeyboxPath ) ;
}
}
if ( attempts > 0 | | allPaths . isEmpty ( ) ) {
break ;
}
// allPaths contains only the encrypted keys now.
passphraseProvider = new JcePBEProtectionRemoverFactory (
passphrasePrompt . getPassphrase (
publicKey . getFingerprint ( ) , userKeyboxPath ) ) ;
}
}
passphrasePrompt . clear ( ) ;
passphrasePrompt . clear ( ) ;