assistant: When generating a gpg secret key, avoid hardcoding the key algorithm and size

This aims to future-proof gpg key generation. OpenPGP is in flux with a
conflict over standards ongoing. It seems not unlikely that different
systems will have different gpg commands that support different algorithms.

This also simplifies the code by using the --quick-gen-key interface rather
than the experimental batch interface. It seems less likely that
--quick-gen-key will break than an experimental interface (whose
documentation I can no longer find).

--quick-gen-key is supported since gpg 2.1.0 (2014).

Sponsored-by: Graham Spencer on Patreon
This commit is contained in:
Joey Hess 2024-01-09 15:31:53 -04:00
parent d37dbd62b8
commit de6a297d36
No known key found for this signature in database
GPG key ID: DB12DB0FF05F8F38
4 changed files with 21 additions and 39 deletions

View file

@ -54,7 +54,7 @@ withNewSecretKey :: (KeyId -> Handler Html) -> Handler Html
withNewSecretKey use = do
cmd <- liftAnnex $ gpgCmd <$> Annex.getGitConfig
userid <- liftIO $ newUserId cmd
liftIO $ genSecretKey cmd RSA "" userid maxRecommendedKeySize
liftIO $ genSecretKey cmd "" userid
results <- M.keys . M.filter (== userid) <$> liftIO (secretKeys cmd)
case results of
[] -> giveup "Failed to generate gpg key!"

View file

@ -2,6 +2,8 @@ git-annex (10.20231228) UNRELEASED; urgency=medium
* info: Added "annex sizes of repositories" table to the overall display.
* import: Sped up import from special remotes.
* assistant: When generating a gpg secret key, avoid hardcoding the
key algorithm and size.
-- Joey Hess <id@joeyh.name> Fri, 29 Dec 2023 11:52:06 -0400

View file

@ -23,8 +23,6 @@ module Utility.Gpg (
findPubKeys,
UserId,
secretKeys,
KeyType(..),
maxRecommendedKeySize,
genSecretKey,
genRandom,
testKeyId,
@ -60,6 +58,8 @@ newtype KeyIds = KeyIds { keyIds :: [KeyId] }
newtype GpgCmd = GpgCmd { unGpgCmd :: String }
type Passphrase = B.ByteString
{- Get gpg command to use, Just what's specified or, if a specific gpg
- command was found at configure time, use it, or otherwise, "gpg". -}
mkGpgCmd :: Maybe FilePath -> GpgCmd
@ -157,7 +157,7 @@ pipeStrict' (GpgCmd cmd) params environ input = do
- the passphrase.
-
- Note that the reader must fully consume gpg's input before returning. -}
feedRead :: (MonadIO m, MonadMask m) => GpgCmd -> [CommandParam] -> B.ByteString -> (Handle -> IO ()) -> (Handle -> m a) -> m a
feedRead :: (MonadIO m, MonadMask m) => GpgCmd -> [CommandParam] -> Passphrase -> (Handle -> IO ()) -> (Handle -> m a) -> m a
feedRead cmd params passphrase feeder reader = do
#ifndef mingw32_HOST_OS
let setup = liftIO $ do
@ -260,49 +260,29 @@ secretKeys cmd = catchDefaultIO M.empty makemap
extract c k (_:rest) =
extract c k rest
type Passphrase = String
type Size = Int
data KeyType = Algo Int | DSA | RSA
{- The maximum key size that gpg currently offers in its UI when
- making keys. -}
maxRecommendedKeySize :: Size
maxRecommendedKeySize = 4096
{- Generates a secret key using the experimental batch mode.
- The key is added to the secret key ring.
- Can take a very long time, depending on system entropy levels.
-}
genSecretKey :: GpgCmd -> KeyType -> Passphrase -> UserId -> Size -> IO ()
genSecretKey (GpgCmd cmd) keytype passphrase userid keysize =
let p = (proc cmd params)
{ std_in = CreatePipe }
in withCreateProcess p (go p)
genSecretKey :: GpgCmd -> Passphrase -> UserId -> IO ()
genSecretKey gpgcmd passphrase userid =
feedRead gpgcmd params passphrase feeder reader
where
params = ["--batch", "--gen-key"]
go p (Just h) _ _ pid = do
hPutStr h $ unlines $ catMaybes
[ Just $ "Key-Type: " ++
case keytype of
DSA -> "DSA"
RSA -> "RSA"
Algo n -> show n
, Just $ "Key-Length: " ++ show keysize
, Just $ "Name-Real: " ++ userid
, Just "Expire-Date: 0"
, if null passphrase
then Nothing
else Just $ "Passphrase: " ++ passphrase
]
hClose h
forceSuccessProcess p pid
go _ _ _ _ _ = error "internal"
params =
[ Param "--batch"
, Param "--quick-gen-key"
, Param userid
, Param "default" -- algo
, Param "default" -- usage
, Param "never" -- expire
]
feeder = hClose
reader = void . hGetContents
{- Creates a block of high-quality random data suitable to use as a cipher.
- It is armored, to avoid newlines, since gpg only reads ciphers up to the
- first newline. -}
genRandom :: GpgCmd -> Bool -> Size -> IO B.ByteString
genRandom :: GpgCmd -> Bool -> Int -> IO B.ByteString
genRandom cmd highQuality size = do
s <- readStrict cmd params
checksize s

View file

@ -5,7 +5,7 @@
<div .modal-header>
<h3>
<img src="@{StaticR activityicon_gif}" alt=""> #
Generating a #{maxRecommendedKeySize} bit GnuPg key.
Generating a GnuPg key.
<div .modal-body>
<p>
Generating a GnuPg key can take a long time. To speed up the process, #