Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ RUN apt-get update && \
libncurses5 \
devscripts \
apksigner \
rpm \
gcc \
g++ \
libc6-dev \
Expand Down
97 changes: 97 additions & 0 deletions autograph.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,102 @@ signers:
=3Atg
-----END PGP PUBLIC KEY BLOCK-----
- id: randompgp-rpmsign
type: gpg2
mode: rpmsign
keyid: 2E20650941C6CCB2725FC377A2B637F535A86009
privatekey: |
-----BEGIN PGP PRIVATE KEY BLOCK-----
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
oOuLM3lEWUfh
=SRmt
-----END PGP PRIVATE KEY BLOCK-----
publickey: |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validated that this is the correct key-pair by running:

gpg --armor --export A2910E4FBEA076009BCDE536DD0A5D99AAAB1F1A

and checked that the output from the private key above matches the public key below.

-----BEGIN PGP PUBLIC KEY BLOCK-----
mQENBFuW9xABCACzCLYHwgGba7hi+lwhD/Hr5qqpg+UuN+88NclYgLWyl1nPpx2D
JvH6p7ASj2P9BzEp0XatXLO4/uPQY2UX9UpWLT5wDGOdX4QCvZvFk4whcXHtcamr
IQFTUjxRSIqvrq4t1h/4z635ztN0C6h5fWCxrCsoPJNQwEG/ZSDNXfwrJbsTIgus
X037WXAzCYKzDZg9dGcUon4F2DHGGGqjOqLsyaGvOvOPddhorESuAJRe6Tl9ijzT
NGc1uXIVEjEa5v9L4DJDqXYJqG35e0UuLkg0Wz4V9RVW/QP5DgnJAMQ8DUkXNHpa
eD1H9Zg/EBt3/85BGCR7u7J6MYvhuVnLIXQ1ABEBAAG0K01vemlsbGEgQXV0b2dy
YXBoIERldiA8bm9yZXBseUBleGFtcGxlLm5ldD6JAVQEEwEKAD4CGwMFCwkIBwMF
FQoJCAsFFgIDAQACHgECF4AWIQSikQ5PvqB2AJvN5TbdCl2ZqqsfGgUCYGs5MgUJ
KmxIIgAKCRDdCl2ZqqsfGuuYB/0b95Wk7JS6hy6d3HGQfiK0V1aFjTJydaCoEnB2
/sT3Z4TvW+mUuAOVkGrpTi01s+difWpDcIE+mxKEOc7t2GB36U7uWV+7aNUMWPLo
fhEAMQLVojonzKtR//9Iy9wqshMNEZ7ed7PA0Ju9DGpQJpUUWahmYHVbVpRtxJ92
M5CJvHNy0HYx8JIM89en4r87kPi/cMsPHo7z1sHvv4MbRw7POpVIO5uYefxd5N4N
HuenYmsfb4KhmVgMjiI6td5xF17tWLXjG3rcKKWRxmPhyKI1XLR5RRer0jLrd4Ge
rdGc96yQEPkQ6Th23V4imOSJBpJ/msSSBFWv5bb3mJdzabDvuQENBFuW9xABCADI
ykOe+xwyVVBNQsy+Bfzk5JsbjWmLcb/7sAy82TVSpq5LMm9v0OFlKMzpD6EjPGe8
wrk1OFHGXkLFhOvprpiZYxOnLbtcLbJ0mkgU3azdLsvBEFrDLv0N8AEdWBptkMCc
ar53W3iLKqxi9eKJCE86K6T7BtCR/NQ+SAUSz4Cv1mZacxMv8FZ5IllvsNmIFyoy
4mQx+tVS5OzNsd1D8gk93NlHQs82od4HI7BxUTnJgB0oZZz58MHCjjHIHCBp71RN
RFRufFArnrHsFkVxHFJH00Yn3WnJi4G5/IuZ8jUmBNFZ7JknwaHn5DX3XbF996MI
YVZtZtnQk0LdMQqVNs/rABEBAAGJATwEGAEKACYCGwwWIQSikQ5PvqB2AJvN5Tbd
Cl2ZqqsfGgUCYGs5DwUJKmxH/wAKCRDdCl2ZqqsfGiC5CAClOM2l9IRIl/iDCn+R
lMW1B12YP4/pPfco0KUMJwlb/lk0uybYjn3o9FbMS7qDlHQ8fn8SreaE4mdJA9Lq
hOahIUeDjfcem5/rPEWNfoiIQELsyvX2DZzjthHwh6+BCBKIekiBRDWj0AYeLtmv
M1vx6uxbvvIZsdOy7zU+jBFIGmL1GOwaaLuTv24EUwGCDq04j/fa5LaVuOSyCXnt
ask+6gjl1qr6zI44+IgZBySoJL86UxPUv4QVXCqgsvKoqMXggPpGEwGIIQec8mGC
Pl9w2LbeqzvlZonAvK/iLPHtaVcMg6fDa/OgKrYcGdfRIhX3u8uiyjLAoOuLM3lE
WUfh
=3Atg
-----END PGP PUBLIC KEY BLOCK-----
- id: pgpsubkey
# TEST 4096-bit RSA public / dev / signing subkey without master
type: gpg2
Expand Down Expand Up @@ -1675,6 +1771,7 @@ authorizations:
- testmarecdsa
- randompgp
- randompgp-debsign
- randompgp-rpmsign
- pgpsubkey
- pgpsubkey-debsign
- dummyrsapss
Expand Down
2 changes: 1 addition & 1 deletion handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -700,7 +700,7 @@ func TestDebug(t *testing.T) {
func TestHandleGetAuthKeyIDs(t *testing.T) {
ag, _ := newTestAutographer(t)

const autographDevAliceKeyIDsJSON = "[\"apk_cert_with_ecdsa_sha256\",\"apk_cert_with_ecdsa_sha256_v3\",\"appkey1\",\"appkey2\",\"dummyrsa\",\"dummyrsapss\",\"extensions-ecdsa\",\"extensions-ecdsa-expired-chain\",\"legacy_apk_with_rsa\",\"normandy\",\"pgpsubkey\",\"pgpsubkey-debsign\",\"randompgp\",\"randompgp-debsign\",\"remote-settings\",\"testapp-android\",\"testapp-android-legacy\",\"testapp-android-v3\",\"testauthenticode\",\"testmar\",\"testmarecdsa\",\"webextensions-rsa\",\"webextensions-rsa-with-recommendation\"]"
const autographDevAliceKeyIDsJSON = "[\"apk_cert_with_ecdsa_sha256\",\"apk_cert_with_ecdsa_sha256_v3\",\"appkey1\",\"appkey2\",\"dummyrsa\",\"dummyrsapss\",\"extensions-ecdsa\",\"extensions-ecdsa-expired-chain\",\"legacy_apk_with_rsa\",\"normandy\",\"pgpsubkey\",\"pgpsubkey-debsign\",\"randompgp\",\"randompgp-debsign\",\"randompgp-rpmsign\",\"remote-settings\",\"testapp-android\",\"testapp-android-legacy\",\"testapp-android-v3\",\"testauthenticode\",\"testmar\",\"testmarecdsa\",\"webextensions-rsa\",\"webextensions-rsa-with-recommendation\"]"

var testcases = []HandlerTestCase{
{
Expand Down
17 changes: 10 additions & 7 deletions signer/gpg2/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
# PGP Signing with GPG2

This signer implements the Pretty Good Privacy signature format and
Debian GPG signing using `debsign`.
This signer implements the Pretty Good Privacy signature format using `gpg2`,
Debian GPG signing using `debsign`, RPM signing using `rpmsign`.

When configured in `gpg2` mode it accepts data on the `/sign/data`
interface and returns armored detached signatures.

In `debsign` mode it accepts files on the `/sign/files` interface and
returns the clearsigned files.

In `rpmsign` mode it accepts RPM files on the `/sign/files` interface and
returns the signed RPM files with embedded GPG signatures.

Example Usage:

``` bash
Expand Down Expand Up @@ -62,8 +65,8 @@ signers:
-----END PGP PUBLIC KEY BLOCK-----
```
The **optional** field `mode` it can be either `gpg2` or
`debsign`. When empty or missing it defaults to `gpg2` and should use
The **optional** field `mode` can be `gpg2`, `debsign`, or `rpmsign`.
When empty or missing it defaults to `gpg2` and should use
the full key fingerprint in the `keyid` field. For example:

```yaml
Expand Down Expand Up @@ -100,14 +103,14 @@ This signer only supports the `/sign/data/` endpoint in `gpg2` mode:
]
```

This signer only supports the `/sign/files/` endpoint in `debsign` mode:
This signer only supports the `/sign/files/` endpoint in both `debsign` and `rpmsign` mode:

``` json
[
{
"input": "",
"keyid": "pgpsubkey-debsign",
"signed_files": [
"files": [
{
"name": "sphinx_1.7.2-1.dsc",
"content": "LS0tLS1CRUdJTiBQR1AgU0lHTkVEIE1FU1NBR0UtLS0tLQpIYXNoOiBTS..."
Expand Down Expand Up @@ -140,7 +143,7 @@ recover the standard armored signature that gnupg expects.
]
```

In `debsign` mode responses are at the field `signed_files`:
In `debsign` and `rpmsign` mode responses are at the field `signed_files`:

```json
[
Expand Down
119 changes: 82 additions & 37 deletions signer/gpg2/gpg2.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ const (
// ModeDebsign represents a signer that signs files with debsign
ModeDebsign = "debsign"

// ModeRPMSign represents a signer that signs RPM files with rpmsign
ModeRPMSign = "rpmsign"

keyRingFilename = "autograph_gpg2_keyring.gpg"
gpgConfFilename = "gpg.conf"

Expand Down Expand Up @@ -101,11 +104,12 @@ func New(conf signer.Configuration, tmpDirPrefix string) (s *GPG2Signer, err err

switch conf.Mode {
case ModeDebsign:
case ModeRPMSign:
case ModeGPG2:
case "": // default to signing in gpg2 mode to preserve backwards compat with old config files
conf.Mode = ModeGPG2
default:
return nil, fmt.Errorf("gpg2: unknown signer mode %q, must be 'gpg2', or 'debsign'", conf.Mode)
return nil, fmt.Errorf("gpg2: unknown signer mode %q, must be 'gpg2', 'debsign', or 'rpmsign'", conf.Mode)
}
s.Mode = conf.Mode

Expand Down Expand Up @@ -136,9 +140,9 @@ func New(conf signer.Configuration, tmpDirPrefix string) (s *GPG2Signer, err err
return nil, fmt.Errorf("gpg2: error creating keyring: %w", err)
}

// debsign lets us to specify a gpg program name (gpg or
// gpg2), but not args. We use a config file to sent them.
if s.Mode == ModeDebsign {
// debsign and rpmsign invoke GPG for signing. We configure GPG
// via gpg.conf in $GNUPGHOME since we can't pass args directly to either tool.
if s.Mode == ModeDebsign || s.Mode == ModeRPMSign {
// write gpg.conf after importing keys, so gpg doesn't try to read stdin for key imports
if err = writeGPGConf(s.tmpDir); err != nil {
return nil, fmt.Errorf("error writing gpg conf: %w", err)
Expand Down Expand Up @@ -371,11 +375,12 @@ func (s *GPG2Signer) GetDefaultOptions() interface{} {
return Options{}
}

// SignFiles uses debsign to gpg2 clearsign multiple named
// *.buildinfo, *.dsc, or *.changes files
// SignFiles uses debsign or rpmsign to sign multiple named files.
// In debsign mode: signs *.buildinfo, *.dsc, or *.changes files (clearsign)
// In rpmsign mode: signs *.rpm files (embedded signature)
func (s *GPG2Signer) SignFiles(inputs []signer.NamedUnsignedFile, options interface{}) (signedFiles []signer.NamedSignedFile, err error) {
if s.Mode != ModeDebsign {
err = fmt.Errorf("gpg2: can only sign multiple files in %s mode", ModeDebsign)
if s.Mode != ModeDebsign && s.Mode != ModeRPMSign {
err = fmt.Errorf("gpg2: can only sign multiple files in %s or %s mode", ModeDebsign, ModeRPMSign)
return
}

Expand All @@ -384,7 +389,7 @@ func (s *GPG2Signer) SignFiles(inputs []signer.NamedUnsignedFile, options interf
// of this directory.
inputsTmpDir, err := os.MkdirTemp(s.tmpDir, "sign_files")
if err != nil {
err = fmt.Errorf("gpg2: error creating tempdir for debsign: %w", err)
err = fmt.Errorf("gpg2: error creating tempdir for %s signing: %w", s.Mode, err)
return
}
defer func() {
Expand All @@ -397,13 +402,20 @@ func (s *GPG2Signer) SignFiles(inputs []signer.NamedUnsignedFile, options interf
var inputFilePaths []string
for i, input := range inputs {
ext := filepath.Ext(input.Name)
if !(ext == ".buildinfo" || ext == ".dsc" || ext == ".changes") {
return nil, fmt.Errorf("gpg2: cannot sign file %d. Files missing extension .buildinfo, .dsc, or .changes", i)
switch s.Mode {
case ModeDebsign:
if !(ext == ".buildinfo" || ext == ".dsc" || ext == ".changes") {
return nil, fmt.Errorf("gpg2: cannot sign file %d. File missing extension .buildinfo, .dsc, or .changes", i)
}
case ModeRPMSign:
if ext != ".rpm" {
return nil, fmt.Errorf("gpg2: cannot sign file %d. File missing extension .rpm", i)
}
}
inputFilePath := filepath.Join(inputsTmpDir, input.Name)
err := os.WriteFile(inputFilePath, input.Bytes, 0644)
if err != nil {
return nil, fmt.Errorf("gpg2: failed to write tempfile %d for debsign to sign: %w", i, err)
return nil, fmt.Errorf("gpg2: failed to write tempfile %d for %s to sign: %w", i, s.Mode, err)
}
inputFilePaths = append(inputFilePaths, inputFilePath)
}
Expand All @@ -412,50 +424,83 @@ func (s *GPG2Signer) SignFiles(inputs []signer.NamedUnsignedFile, options interf
serializeSigning.Lock()
defer serializeSigning.Unlock()

args := append([]string{
// "Do not read any configuration files. This can only be used as the first option given on the command-line."
"--no-conf",
// "Specify the key ID to be used for signing; overrides any -m and -e options."
// debsign prefers the pub key fingerprint: https://github.com/Debian/devscripts/blob/16f9a6d24f4bd564c315f81b89e08c3b4fb76f13/scripts/debsign.sh#L389
"-k", s.KeyID,
// "Recreate signature"
"--re-sign",
}, inputFilePaths...)
debsignCmd := exec.Command("debsign", args...)
debsignCmd.Env = append(os.Environ(),
var cmd *exec.Cmd
var passphraseRepeat int

switch s.Mode {
case ModeDebsign:
args := append([]string{
// "Do not read any configuration files. This can only be used as the first option given on the command-line."
"--no-conf",
// "Specify the key ID to be used for signing; overrides any -m and -e options."
// debsign prefers the pub key fingerprint: https://github.com/Debian/devscripts/blob/16f9a6d24f4bd564c315f81b89e08c3b4fb76f13/scripts/debsign.sh#L389
"-k", s.KeyID,
// "Recreate signature"
"--re-sign",
}, inputFilePaths...)
cmd = exec.Command("debsign", args...)
// debsign can call gpg multiple times per file
passphraseRepeat = len(inputFilePaths) * 4
Comment on lines +442 to +443
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate more on why we are multiplying by 4 here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

passphrasesForStdin := strings.Repeat(fmt.Sprintf("%s\n", s.passphrase), len(inputFilePaths)*4) because this was the code before.

case ModeRPMSign:
// We pass passphrase-fd 3 via _gpg_sign_cmd_extra_args to override
// passphrase-fd 0 from gpg.conf. This is because rpmsign uses stdin
// to pipe header data to GPG for signing but not the passphrase. Having
// passphrase-fd 0 in gpg.conf causes GPG to consume header data as passphrase,
// corrupting the data getting signed since the start will be omitted.
// We set up fd 3 ourselves via cmd.ExtraFiles and pass the passphrase through that.
// See https://github.com/rpm-software-management/rpm/blob/4623d4601ee83b5e0ecd16dd3f54d2182b519b30/sign/rpmgensig.c#L254
args := append([]string{
"--addsign",
"--key-id", s.KeyID,
"--define", "__gpg /usr/bin/gpg",
"--define", "_gpg_sign_cmd_extra_args --passphrase-fd 3",
}, inputFilePaths...)
Comment on lines +452 to +457
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Eijebong, @alexcottner and I checked the documentation and do not see what --define does and how --passphrase-fd is passed to gpg. Can you point to some documentations on both?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--define is an rpm option:

$ rpmsign --help
Usage: rpmsign [OPTION...]

Signature options:
      --addsign                    sign package(s), adding a new signature
      --resign                     resign package(s), deleting any previous signatures
      --delsign                    delete package signatures
      --delfilesign                delete IMA and fsverity file signatures
      --rpmv3                      request legacy rpm v3 header+payload signatures on v4 packages
      --rpmv4                      request legacy rpm v4 header signatures on v6 packages
      --rpmv6                      request rpm v6 header signature on v4 packages
      --signverity                 generate fsverity signatures for package(s) files
      --verityalgo=<algorithm>     algorithm to use for verity signatures, default sha256
      --certpath=<cert>            use file signing cert <cert>
      --fskpath=<key>              use file signing key <key>
      --fskpass                    prompt for file signing key password

Common options for all rpm modes and executables:
  -D, --define='MACRO EXPR'        define MACRO with value EXPR
      --undefine=MACRO             undefine MACRO
  -E, --eval='EXPR'                print macro expansion of EXPR
      --target=CPU-VENDOR-OS       Specify target platform
      --macros=<FILE:...>          read <FILE:...> instead of default file(s)
      --load=<FILE>                load a single macro file
      --noplugins                  don't enable any plugins
      --nodigest                   don't verify package digest(s)
      --nosignature                don't verify package signature(s)
      --rcfile=<FILE:...>          read <FILE:...> instead of default file(s)
  -r, --root=ROOT                  use ROOT as top level directory (default: "/")
      --dbpath=DIRECTORY           use database in DIRECTORY
      --querytags                  display known query tags
      --showrc                     display final rpmrc and macro configuration
      --quiet                      provide less detailed output
  -v, --verbose                    provide more detailed output
      --version                    print the version of rpm being used

Options implemented via popt alias/exec:
      --key-id=<id>                key id/name to sign with
      --digest-algo=<algorithm>    override default digest algorithm (eg sha1/sha256)

Help options:
  -?, --help                       Show this help message
      --usage                      Display brief usage message

rpm --showrc shows how _gpg_sign_cmd_extra_args is used, e.g.

-13: __gpg_sign_cmd	%{shescape:%{__gpg}} 
	--no-verbose --no-armor --no-secmem-warning 
	%{?_gpg_path:--homedir %{shescape:%{_gpg_path}}} 
	%{?_gpg_digest_algo:--digest-algo=%{_gpg_digest_algo}} 
	%{?_gpg_sign_cmd_extra_args} 
	%{?_openpgp_sign_id:-u %{shescape:%{_openpgp_sign_id}}} 
	-sbo %{shescape:%{2}} -- %{shescape:%{1}}

cmd = exec.Command("rpmsign", args...)
// rpmsign calls gpg once per file
passphraseRepeat = len(inputFilePaths)
}

cmd.Env = append(os.Environ(),
fmt.Sprintf("GNUPGHOME=%s", s.tmpDir),
)
stdin, err := debsignCmd.StdinPipe()

passphraseReader, passphraseWriter, err := os.Pipe()
if err != nil {
return nil, fmt.Errorf("gpg2: failed to create stdin pipe for debsign cmd: %w", err)
return nil, fmt.Errorf("gpg2: failed to create %s passphrase pipe: %w", s.Mode, err)
}
defer passphraseReader.Close()

// write passphrase multiple times to stdin
// our gpg.conf prompts for the passphrase on each gpg call
// and debsign can call gpg multiple times per file
passphrasesForStdin := strings.Repeat(fmt.Sprintf("%s\n", s.passphrase), len(inputFilePaths)*4)
if _, err = io.WriteString(stdin, passphrasesForStdin); err != nil {
return nil, fmt.Errorf("gpg2: failed to write passphrase to stdin pipe for debsign cmd: %w", err)
// Write passphrase to pipe (multiple times, once per signing operation)
passphrases := strings.Repeat(fmt.Sprintf("%s\n", s.passphrase), passphraseRepeat)
if _, err = io.WriteString(passphraseWriter, passphrases); err != nil {
passphraseWriter.Close()
return nil, fmt.Errorf("gpg2: failed to write %s passphrase to pipe: %w", s.Mode, err)
}
if err = stdin.Close(); err != nil {
return nil, fmt.Errorf("gpg2: failed to close to stdin pipe for debsign cmd: %w", err)
passphraseWriter.Close()

// For rpmsign, pass passphrase via fd 3, for debsign via fd 0 (stdin)
if s.Mode == ModeRPMSign {
cmd.ExtraFiles = []*os.File{passphraseReader}
} else {
cmd.Stdin = passphraseReader
}
out, err := debsignCmd.CombinedOutput()

out, err := cmd.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("gpg2: failed to debsign inputs %s\n%s", err, out)
return nil, fmt.Errorf("gpg2: failed to %s inputs: %w\n%s", s.Mode, err, out)
}

// read the signed tempfiles
for i, inputFilePath := range inputFilePaths {
signedFileBytes, err := os.ReadFile(inputFilePath)
if err != nil {
return nil, fmt.Errorf("gpg2: failed to read %d %q signed by debsign: %w", i, inputFilePath, err)
return nil, fmt.Errorf("gpg2: failed to read %d %q signed by %s: %w", i, inputFilePath, s.Mode, err)
}
signedFiles = append(signedFiles, signer.NamedSignedFile{
Name: inputs[i].Name,
Bytes: signedFileBytes,
})
}
log.Debugf("debsign output:\n%s\n", string(out))
log.Debugf("%s output:\n%s\n", s.Mode, string(out))
return signedFiles, nil
}
Loading
Loading