From bcf73ec04a6d6701f7f2090bc8569e8d680dc72a Mon Sep 17 00:00:00 2001 From: Tim Dysinger Date: Wed, 30 Sep 2015 05:25:57 +0000 Subject: [PATCH 1/2] GPG signing with `stack sig sign sdist` and `stack upload --sign` --- src/Stack/Package.hs | 25 +++++++- src/Stack/Sig.hs | 65 +++++++++++++++++++ src/Stack/Sig/GPG.hs | 102 ++++++++++++++++++++++++++++++ src/Stack/Sig/Sign.hs | 137 +++++++++++++++++++++++++++++++++++++++++ src/Stack/Types.hs | 1 + src/Stack/Types/Sig.hs | 99 +++++++++++++++++++++++++++++ src/Stack/Upload.hs | 8 ++- src/main/Main.hs | 65 ++++++++++++++++--- stack.cabal | 6 ++ 9 files changed, 499 insertions(+), 9 deletions(-) create mode 100644 src/Stack/Sig.hs create mode 100644 src/Stack/Sig/GPG.hs create mode 100644 src/Stack/Sig/Sign.hs create mode 100644 src/Stack/Types/Sig.hs diff --git a/src/Stack/Package.hs b/src/Stack/Package.hs index 04f0cb4082..d4ee603443 100644 --- a/src/Stack/Package.hs +++ b/src/Stack/Package.hs @@ -33,9 +33,14 @@ module Stack.Package ,packageIdentifier ,autogenDir ,checkCabalFileName - ,printCabalFileWarning) + ,printCabalFileWarning + ,cabalFilePackageId) where +#if __GLASGOW_HASKELL__ < 710 +import Control.Applicative (Applicative, (<$>), (<*>)) +#endif + import Control.Arrow ((&&&)) import Control.Exception hiding (try,catch) import Control.Monad @@ -60,16 +65,21 @@ import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (decodeUtf8, decodeUtf8With) import Data.Text.Encoding.Error (lenientDecode) +import Data.Version (showVersion) import Distribution.Compiler import Distribution.ModuleName (ModuleName) import qualified Distribution.ModuleName as Cabal import Distribution.Package hiding (Package,PackageName,packageName,packageVersion,PackageIdentifier) +import qualified Distribution.Package as D import Distribution.PackageDescription hiding (FlagName) +import qualified Distribution.PackageDescription as D import Distribution.PackageDescription.Parse +import qualified Distribution.PackageDescription.Parse as D import Distribution.ParseUtils import Distribution.Simple.Utils import Distribution.System (OS (..), Arch, Platform (..)) import Distribution.Text (display, simpleParse) +import qualified Distribution.Verbosity as D import Path as FL import Path.Extra import Path.Find @@ -1094,3 +1104,16 @@ resolveDirOrWarn :: (MonadThrow m,MonadIO m,MonadLogger m,MonadReader (Path Abs => FilePath.FilePath -> m (Maybe (Path Abs Dir)) resolveDirOrWarn = resolveOrWarn "Directory" resolveDirMaybe + +-- | Extract the @PackageIdentifier@ given an exploded haskell package +-- path. +cabalFilePackageId + :: (Applicative m, MonadIO m, MonadThrow m) + => Path Abs File -> m PackageIdentifier +cabalFilePackageId fp = do + pkgDescr <- liftIO (D.readPackageDescription D.silent $ toFilePath fp) + (toStackPI . D.package . D.packageDescription) pkgDescr + where + toStackPI (D.PackageIdentifier (D.PackageName name) ver) = + PackageIdentifier <$> parsePackageNameFromString name <*> + parseVersionFromString (showVersion ver) diff --git a/src/Stack/Sig.hs b/src/Stack/Sig.hs new file mode 100644 index 0000000000..37af42ce62 --- /dev/null +++ b/src/Stack/Sig.hs @@ -0,0 +1,65 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TemplateHaskell #-} + +{-| +Module : Stack.Sig +Description : GPG Signatures for Stack +Copyright : (c) FPComplete.com, 2015 +License : BSD3 +Maintainer : Tim Dysinger +Stability : experimental +Portability : POSIX +-} + +module Stack.Sig + ( module Sig + , sigCmdName + , sigSignCmdName + , sigSignHackageCmdName + , sigSignHackageOpts + , sigSignSdistCmdName + , sigSignSdistOpts + ) + where + +import Options.Applicative +import Stack.Sig.GPG as Sig +import Stack.Sig.Sign as Sig + +-- | The command name for dealing with signatures. +sigCmdName :: String +sigCmdName = "sig" + +-- | The command name for signing packages. +sigSignCmdName :: String +sigSignCmdName = "sign" + +-- | The command name for signing an sdist package file. +sigSignSdistCmdName :: String +sigSignSdistCmdName = "sdist" + +-- | The command name for signing all your packages from hackage.org. +sigSignHackageCmdName :: String +sigSignHackageCmdName = "hackage" + +-- | The URL of the running signature service to use (sig-service) +url :: Parser String +url = strOption + (long "url" <> + short 'u' <> + metavar "URL" <> + showDefault <> + value "https://sig.commercialhaskell.org") + +-- | Signature sign (sdist) options +sigSignSdistOpts :: Parser (String, String) +sigSignSdistOpts = helper <*> + ((,) <$> url <*> + argument str (metavar "PATH")) + +-- | Signature sign (hackage) options +sigSignHackageOpts :: Parser (String, String) +sigSignHackageOpts = helper <*> + ((,) <$> url <*> + argument str (metavar "USER")) diff --git a/src/Stack/Sig/GPG.hs b/src/Stack/Sig/GPG.hs new file mode 100644 index 0000000000..d19f878e12 --- /dev/null +++ b/src/Stack/Sig/GPG.hs @@ -0,0 +1,102 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE RecordWildCards #-} + +{-| +Module : Stack.Sig.GPG +Description : GPG Functions +Copyright : (c) FPComplete.com, 2015 +License : BSD3 +Maintainer : Tim Dysinger +Stability : experimental +Portability : POSIX +-} + +module Stack.Sig.GPG (fullFingerprint, signPackage, verifyFile) + where + +#if __GLASGOW_HASKELL__ < 710 +import Control.Applicative ((<$>)) +#endif + +import Control.Monad.Catch (MonadThrow, throwM) +import Control.Monad.IO.Class (MonadIO, liftIO) +import qualified Data.ByteString.Char8 as C +import Data.Char (isSpace) +import Data.List (find) +import Data.Monoid ((<>)) +import qualified Data.Text as T +import Path +import Stack.Types +import System.Exit (ExitCode(..)) +import System.Process (readProcessWithExitCode) + +-- | Extract the full long @fingerprint@ given a short (or long) +-- @fingerprint@ +fullFingerprint + :: (Monad m, MonadIO m, MonadThrow m) + => Fingerprint -> m Fingerprint +fullFingerprint (Fingerprint fp) = do + (code,out,err) <- + liftIO + (readProcessWithExitCode "gpg" ["--fingerprint", T.unpack fp] []) + if code /= ExitSuccess + then throwM (GPGFingerprintException (out ++ "\n" ++ err)) + else maybe + (throwM + (GPGFingerprintException + ("unable to extract full fingerprint from output:\n " <> + out))) + return + (let hasFingerprint = + (==) ["Key", "fingerprint", "="] . take 3 + fingerprint = + T.filter (not . isSpace) . T.pack . unwords . drop 3 + in Fingerprint . fingerprint <$> + find hasFingerprint (map words (lines out))) + +-- | Sign a file path with GPG, returning the @Signature@. +signPackage + :: (Monad m, MonadIO m, MonadThrow m) + => Path Abs File -> m Signature +signPackage path = do + (code,out,err) <- + liftIO + (readProcessWithExitCode + "gpg" + [ "--output" + , "-" + , "--use-agent" + , "--detach-sig" + , "--armor" + , toFilePath path] + []) + if code /= ExitSuccess + then throwM (GPGSignException (out ++ "\n" ++ err)) + else return (Signature (C.pack out)) + +-- | Verify the @Signature@ of a file path returning the +-- @Fingerprint@. +verifyFile + :: (Monad m, MonadIO m, MonadThrow m) + => Signature -> Path Abs File -> m Fingerprint +verifyFile (Signature signature) path = do + let process = + readProcessWithExitCode + "gpg" + ["--verify", "-", toFilePath path] + (C.unpack signature) + (code,out,err) <- liftIO process + if code /= ExitSuccess + then throwM (GPGVerifyException (out ++ "\n" ++ err)) + else maybe + (throwM + (GPGFingerprintException + ("unable to extract short fingerprint from output\n: " <> + out))) + return + (let hasFingerprint = + (==) ["gpg:", "Signature", "made"] . take 3 + fingerprint = T.pack . last + in Fingerprint . fingerprint <$> + find hasFingerprint (map words (lines err))) diff --git a/src/Stack/Sig/Sign.hs b/src/Stack/Sig/Sign.hs new file mode 100644 index 0000000000..c0242108e2 --- /dev/null +++ b/src/Stack/Sig/Sign.hs @@ -0,0 +1,137 @@ +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE TemplateHaskell #-} + +{-| +Module : Stack.Sig.Sign +Description : Signing Packages +Copyright : (c) FPComplete.com, 2015 +License : BSD3 +Maintainer : Tim Dysinger +Stability : experimental +Portability : POSIX +-} + +module Stack.Sig.Sign (sign, signTarBytes) where + +import qualified Codec.Archive.Tar as Tar +import qualified Codec.Compression.GZip as GZip +import Control.Monad (when) +import Control.Monad.Catch +import Control.Monad.IO.Class +import Control.Monad.Logger +import Control.Monad.Trans.Control +import qualified Data.ByteString.Lazy as BS +import qualified Data.ByteString.Lazy as L +import Data.Monoid ((<>)) +import qualified Data.Text as T +import Data.UUID (toString) +import Data.UUID.V4 (nextRandom) +import Network.HTTP.Conduit + (Response(..), RequestBody(..), Request(..), httpLbs, newManager, + tlsManagerSettings) +import Network.HTTP.Download +import Network.HTTP.Types (status200, methodPut) +import Path +import Path.IO +import Stack.Package +import qualified Stack.Sig.GPG as GPG +import Stack.Types +import qualified System.FilePath as FP + +-- | Sign a haskell package with the given url of the signature +-- service and a path to a tarball. +sign + :: (MonadCatch m, MonadBaseControl IO m, MonadIO m, MonadMask m, MonadLogger m, MonadThrow m) + => Maybe (Path Abs Dir) -> String -> Path Abs File -> m () +sign Nothing _ _ = throwM SigNoProjectRootException +sign (Just projectRoot) url filePath = do + withStackWorkTempDir + projectRoot + (\tempDir -> + do bytes <- + liftIO + (fmap + GZip.decompress + (BS.readFile (toFilePath filePath))) + maybePath <- extractCabalFile tempDir (Tar.read bytes) + case maybePath of + Nothing -> throwM SigInvalidSDistTarBall + Just cabalPath -> do + pkg <- cabalFilePackageId (tempDir cabalPath) + signPackage url pkg filePath) + where + extractCabalFile tempDir (Tar.Next entry entries) = do + case Tar.entryContent entry of + (Tar.NormalFile lbs _) -> + case FP.splitFileName (Tar.entryPath entry) of + (folder,file) + | length (FP.splitDirectories folder) == 1 && + FP.takeExtension file == ".cabal" -> do + cabalFile <- parseRelFile file + liftIO + (BS.writeFile + (toFilePath (tempDir cabalFile)) + lbs) + return (Just cabalFile) + (_,_) -> extractCabalFile tempDir entries + _ -> extractCabalFile tempDir entries + extractCabalFile _ _ = return Nothing + +-- | Sign a haskell package with the given url to the signature +-- service, a package tarball path (package tarball name) and a lazy +-- bytestring of bytes that represent the tarball bytestream. The +-- function will write the bytes to the path in a temp dir and sign +-- the tarball with GPG. +signTarBytes + :: (MonadCatch m, MonadBaseControl IO m, MonadIO m, MonadMask m, MonadLogger m, MonadThrow m) + => Maybe (Path Abs Dir) -> String -> Path Rel File -> L.ByteString -> m () +signTarBytes Nothing _ _ _ = throwM SigNoProjectRootException +signTarBytes (Just projectRoot) url tarPath bs = + withStackWorkTempDir + projectRoot + (\tempDir -> + do let tempTarBall = tempDir tarPath + liftIO (L.writeFile (toFilePath tempTarBall) bs) + sign (Just projectRoot) url tempTarBall) + +-- | Sign a haskell package given the url to the signature service, a +-- @PackageIdentifier@ and a file path to the package on disk. +signPackage + :: (MonadCatch m, MonadBaseControl IO m, MonadIO m, MonadMask m, MonadLogger m, MonadThrow m) + => String -> PackageIdentifier -> Path Abs File -> m () +signPackage url pkg filePath = do + $logInfo ("GPG signing " <> T.pack (toFilePath filePath)) + sig@(Signature signature) <- GPG.signPackage filePath + let (PackageIdentifier n v) = pkg + name = show n + version = show v + verify <- GPG.verifyFile sig filePath + fingerprint <- GPG.fullFingerprint verify + req <- + parseUrl + (url <> "/upload/signature/" <> name <> "/" <> version <> "/" <> + T.unpack (fingerprintSample fingerprint)) + let put = + req + { method = methodPut + , requestBody = RequestBodyBS signature + } + mgr <- liftIO (newManager tlsManagerSettings) + res <- liftIO (httpLbs put mgr) + when + (responseStatus res /= status200) + (throwM (GPGSignException "unable to sign & upload package")) + +withStackWorkTempDir + :: (MonadCatch m, MonadIO m, MonadMask m, MonadLogger m) + => Path Abs Dir -> (Path Abs Dir -> m ()) -> m () +withStackWorkTempDir projectRoot f = do + uuid <- liftIO nextRandom + uuidPath <- parseRelDir (toString uuid) + let tempDir = projectRoot workDirRel $(mkRelDir "tmp") uuidPath + bracket + (createTree tempDir) + (const (removeTree tempDir)) + (const (f tempDir)) diff --git a/src/Stack/Types.hs b/src/Stack/Types.hs index 4a752f03bf..b83e08441c 100644 --- a/src/Stack/Types.hs +++ b/src/Stack/Types.hs @@ -17,3 +17,4 @@ import Stack.Types.Image as X import Stack.Types.Build as X import Stack.Types.Package as X import Stack.Types.Compiler as X +import Stack.Types.Sig as X diff --git a/src/Stack/Types/Sig.hs b/src/Stack/Types/Sig.hs new file mode 100644 index 0000000000..9dee12c994 --- /dev/null +++ b/src/Stack/Types/Sig.hs @@ -0,0 +1,99 @@ +{-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} + +{-| +Module : Stack.Types.Sig +Description : Signature Types +Copyright : (c) FPComplete.com, 2015 +License : BSD3 +Maintainer : Tim Dysinger +Stability : experimental +Portability : POSIX +-} + +module Stack.Types.Sig + (Signature(..), Fingerprint(..), SigException(..)) + where + +import Control.Exception (Exception) +import Data.Aeson (Value(..), ToJSON(..), FromJSON(..)) +import Data.ByteString (ByteString) +import qualified Data.ByteString as SB +import Data.Char (isDigit, isAlpha, isSpace) +import Data.Monoid ((<>)) +import Data.String (IsString(..)) +import Data.Text (Text) +import qualified Data.Text as T +import qualified Data.Text.Encoding as T +import Data.Typeable (Typeable) +import Stack.Types.PackageName + +-- | A GPG signature. +newtype Signature = + Signature ByteString + deriving (Ord,Eq) + +instance Show Signature where + show (Signature s) = "Signature " ++ + (if SB.length s > 140 + then show (SB.take 140 s) ++ + "..." + else show (SB.take 140 s)) + +-- | The GPG fingerprint. +newtype Fingerprint = Fingerprint + { fingerprintSample :: Text + } deriving (Eq,Ord,Show) + +instance FromJSON Fingerprint where + parseJSON j = do + s <- parseJSON j + let withoutSpaces = T.filter (not . isSpace) s + if T.null withoutSpaces || + T.all + (\c -> + isAlpha c || isDigit c || isSpace c) + withoutSpaces + then return (Fingerprint withoutSpaces) + else fail ("Expected fingerprint, but got: " ++ T.unpack s) + +instance ToJSON Fingerprint where + toJSON (Fingerprint txt) = String txt + +instance IsString Fingerprint where + fromString = Fingerprint . T.pack + +instance FromJSON (Aeson PackageName) where + parseJSON j = do + s <- parseJSON j + case (parsePackageName . T.encodeUtf8) s of + Just name -> return (Aeson name) + Nothing -> fail ("Invalid package name: " <> T.unpack s) + +-- | Handy wrapper for orphan instances. +newtype Aeson a = Aeson + { _unAeson :: a + } deriving (Ord,Eq) + +-- | Exceptions +data SigException + = GPGFingerprintException String + | GPGSignException String + | GPGVerifyException String + | SigInvalidSDistTarBall + | SigNoProjectRootException + | SigServiceException String + deriving (Typeable) + +instance Exception SigException + +instance Show SigException where + show (GPGFingerprintException e) = + "Error extracting a GPG fingerprint " <> e + show (GPGSignException e) = "Error signing with GPG " <> e + show (GPGVerifyException e) = "Error verifying with GPG " <> e + show SigNoProjectRootException = "Missing Project Root" + show SigInvalidSDistTarBall = "Invalid sdist tarball" + show (SigServiceException e) = "Error with the Signature Service " <> e diff --git a/src/Stack/Upload.hs b/src/Stack/Upload.hs index 1c5e125123..8643018666 100644 --- a/src/Stack/Upload.hs +++ b/src/Stack/Upload.hs @@ -4,7 +4,8 @@ -- | Provide ability to upload tarballs to Hackage. module Stack.Upload ( -- * Upload - mkUploader + nopUploader + , mkUploader , Uploader , upload , uploadBytes @@ -181,6 +182,11 @@ promptPassword = do putStrLn "" return passwd +nopUploader :: Config -> UploadSettings -> IO Uploader +nopUploader _ _ = return (Uploader nop) + where nop :: String -> L.ByteString -> IO () + nop _ _ = return () + -- | Turn the given settings into an @Uploader@. -- -- Since 0.1.0.0 diff --git a/src/main/Main.hs b/src/main/Main.hs index cf3ce515b5..3188dfaf49 100644 --- a/src/main/Main.hs +++ b/src/main/Main.hs @@ -76,6 +76,7 @@ import Stack.Package (getCabalFileName) import qualified Stack.PackageIndex import Stack.SDist (getSDistTarball, checkSDistTarball, checkSDistTarball') import Stack.Setup +import qualified Stack.Sig as Sig import Stack.Solver (solveExtraDeps) import Stack.Types import Stack.Types.Internal @@ -238,10 +239,13 @@ main = withInterpreterArgs stackProgName $ \args isInterpreter -> do "Upload a package to Hackage" cmdFooter uploadCmd - ((,,) + ((,,,) <$> many (strArgument $ metavar "TARBALL/DIR") <*> optional pvpBoundsOption - <*> ignoreCheckSwitch) + <*> ignoreCheckSwitch + <*> flag False True + (long "sign" <> + help "GPG sign & submit signature")) addCommand' "sdist" "Create source distribution tarballs" cmdFooter @@ -389,7 +393,21 @@ main = withInterpreterArgs stackProgName $ \args isInterpreter -> do "Generate HPC report a combined HPC report" cmdFooter hpcReportCmd - hpcReportOptsParser)) + hpcReportOptsParser) + addSubCommands' + Sig.sigCmdName + "Subcommands specific to package signatures (EXPERIMENTAL)" + cmdFooter + (do addSubCommands' + Sig.sigSignCmdName + "Sign a a single package or all your packages" + cmdFooter + (do addCommand' + Sig.sigSignSdistCmdName + "Sign a single sdist package file" + cmdFooter + sigSignSdistCmd + Sig.sigSignSdistOpts))) case eGlobalRun of Left (exitCode :: ExitCode) -> do when isInterpreter $ @@ -814,9 +832,9 @@ upgradeCmd (fromGit, repo) go = withConfigAndLock go $ #endif -- | Upload to Hackage -uploadCmd :: ([String], Maybe PvpBounds, Bool) -> GlobalOpts -> IO () -uploadCmd ([], _, _) _ = error "To upload the current package, please run 'stack upload .'" -uploadCmd (args, mpvpBounds, ignoreCheck) go = do +uploadCmd :: ([String], Maybe PvpBounds, Bool, Bool) -> GlobalOpts -> IO () +uploadCmd ([], _, _, _) _ = error "To upload the current package, please run 'stack upload .'" +uploadCmd (args, mpvpBounds, ignoreCheck, shouldSign) go = do let partitionM _ [] = return ([], []) partitionM f (x:xs) = do r <- f x @@ -827,6 +845,7 @@ uploadCmd (args, mpvpBounds, ignoreCheck) go = do unless (null invalid) $ error $ "stack upload expects a list sdist tarballs or cabal directories. Can't find " ++ show invalid + (_,lc) <- liftIO $ loadConfigWithOpts go let getUploader :: (HasStackRoot config, HasPlatform config, HasConfig config) => StackT config IO Upload.Uploader getUploader = do config <- asks getConfig @@ -834,17 +853,37 @@ uploadCmd (args, mpvpBounds, ignoreCheck) go = do let uploadSettings = Upload.setGetManager (return manager) Upload.defaultUploadSettings liftIO $ Upload.mkUploader config uploadSettings + sigServiceUrl = "https://sig.commercialhaskell.org/" withBuildConfigAndLock go $ \_ -> do uploader <- getUploader unless ignoreCheck $ mapM_ (parseRelAsAbsFile >=> checkSDistTarball) files - liftIO $ forM_ files (canonicalizePath >=> Upload.upload uploader) + forM_ + files + (\file -> + do tarFile <- parseRelAsAbsFile file + liftIO + (Upload.upload uploader (toFilePath tarFile)) + when + shouldSign + (Sig.sign + (lcProjectRoot lc) + sigServiceUrl + tarFile)) unless (null dirs) $ forM_ dirs $ \dir -> do pkgDir <- parseRelAsAbsDir dir (tarName, tarBytes) <- getSDistTarball mpvpBounds pkgDir unless ignoreCheck $ checkSDistTarball' tarName tarBytes liftIO $ Upload.uploadBytes uploader tarName tarBytes + tarPath <- parseRelFile tarName + when + shouldSign + (Sig.signTarBytes + (lcProjectRoot lc) + sigServiceUrl + tarPath + tarBytes) sdistCmd :: ([String], Maybe PvpBounds, Bool) -> GlobalOpts -> IO () sdistCmd (dirs, mpvpBounds, ignoreCheck) go = @@ -1006,6 +1045,18 @@ imgDockerCmd rebuild go@GlobalOpts{..} = Image.stageContainerImageArtifacts) (Just Image.createContainerImageFromStage) +sigSignSdistCmd :: (String, String) -> GlobalOpts -> IO () +sigSignSdistCmd (url,path) go = do + withConfigAndLock + go + (do (manager,lc) <- liftIO (loadConfigWithOpts go) + tarBall <- parseRelAsAbsFile path + runStackTGlobal + manager + (lcConfig lc) + go + (Sig.sign (lcProjectRoot lc) url tarBall)) + -- | Load the configuration with a manager. Convenience function used -- throughout this module. loadConfigWithOpts :: GlobalOpts -> IO (Manager,LoadConfig (StackLoggingT IO)) diff --git a/stack.cabal b/stack.cabal index 7593c88097..b340ba09df 100644 --- a/stack.cabal +++ b/stack.cabal @@ -88,6 +88,7 @@ library Stack.Types.PackageName Stack.Types.TemplateName Stack.Types.Version + Stack.Types.Sig Stack.Types.StackT Stack.Types.Build Stack.Types.Package @@ -99,6 +100,9 @@ library Stack.Build.Installed Stack.Build.Source Stack.Build.Target + Stack.Sig + Stack.Sig.GPG + Stack.Sig.Sign Stack.Upgrade Stack.Upload System.Process.Read @@ -195,6 +199,8 @@ library , word8 , hastache , project-template >= 0.2 + , email-validate >=2.0 + , uuid if os(windows) cpp-options: -DWINDOWS build-depends: Win32 From e156f8ae26d3c23fa15d2badec0ea7023fc0f582 Mon Sep 17 00:00:00 2001 From: Tim Dysinger Date: Fri, 27 Nov 2015 16:51:38 +0000 Subject: [PATCH 2/2] add notes about `stack sig sign` and `stack upload --sign` --- ChangeLog.md | 2 ++ doc/GUIDE.md | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index d873165fea..0c90f8395e 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -12,6 +12,8 @@ Other enhancements: [#1397](https://github.com/commercialhaskell/stack/issues/1397) * Only run benchmarks specified as build targets [#1412](https://github.com/commercialhaskell/stack/issues/1412) +* Add optional GPG signing on `stack upload --sign` or with + `stack sig sign ...` Bug fixes: diff --git a/doc/GUIDE.md b/doc/GUIDE.md index 756f50f525..691ec96f2f 100644 --- a/doc/GUIDE.md +++ b/doc/GUIDE.md @@ -1705,6 +1705,9 @@ users. Here's a quick rundown: * `stack upload` uploads an sdist to Hackage. In the future, it will also perform automatic GPG signing of your packages for additional security, when configured. + * `--sign` provides a way to GPG sign your package & submit the result to + sig.commercialhaskell.org for storage in the sig-archive git + repo. (Signatures will be used later to verify package integrity.) * `stack upgrade` will build a new version of stack from source. * `--git` is a convenient way to get the most recent version from master for those testing and living on the bleeding edge. @@ -1714,6 +1717,10 @@ users. Here's a quick rundown: but it can be useful if you're trying to test a specific bugfix. * `stack list-dependencies` lists all of the packages and versions used for a project +* `stack sig` subcommand can help you with GPG signing & verification + * `sign` will sign an sdist tarball and submit the signature to + sig.commercialhaskell.org for storage in the sig-archive git repo. + (Signatures will be used later to verify package integrity.) ## Debugging