Skip to content

Commit 8d8f204

Browse files
committed
Bug 2009927 - Add support for rpmsign in the gpg2 signer
This mimics what autograph is doing for debsign. While the signature of RPM is simple gpg, we can't use the plain gpg2 signer for it because we cannot attach a detached signature easily, all the tools that are available for this would require some fairly serious hacking. The addition itself is fairly straightforward, there's a bit of plumbing to add it as a signer type, but overall it's sharing all the same code paths as the debsign signer. The one main difference is how we're passing the passphrase to it... The key's passphrase must be given to gpg2 itself, which both debsign and rpmsign would normally do through pinentry. The config says to use the caller (rpm/debsign) to get the passphrase from (`pinentry-mode loopback` in gpg.conf). Autograph uses that and `passphrase-fd 0` to write the passphrase onto debsign's stdin which is forwarded to the gpg subprocess and everything works fine there. Rpmsign on the other hand invokes gpg and changes the stdin for it to a pipe in which it passes the value to be signed [1]. That means that when we try to write a passphrase onto rpmsign's stdin, it ends up in the "void" since it's not forwarded to the gpg subprocess. And `passphrase-fd 0` means that it will read the value to be signed instead of the actual passphrase which as you can expect doesn't go very well... Instead of doing that, we force the fd 3 for rpmsign to be a pipe on our control, and we tell gpg to read from that for the passphrase, and write it there. It used to do that automatically on its own (setup a fd 3 in which one could pipe in a passphrase) until rpm 4.13 [2] but got removed because gpg 2.1 doesn't allow `passphrase-fd` by default anymore. [1]: https://github.com/rpm-software-management/rpm/blob/4623d4601ee83b5e0ecd16dd3f54d2182b519b30/sign/rpmgensig.c#L254 [2]: rpm-software-management/rpm@0bce5fc
1 parent 7638e3a commit 8d8f204

File tree

7 files changed

+363
-54
lines changed

7 files changed

+363
-54
lines changed

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ RUN apt-get update && \
1919
libncurses5 \
2020
devscripts \
2121
apksigner \
22+
rpm \
2223
gcc \
2324
g++ \
2425
libc6-dev \

autograph.yaml

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,102 @@ signers:
918918
=3Atg
919919
-----END PGP PUBLIC KEY BLOCK-----
920920
921+
- id: randompgp-rpmsign
922+
type: gpg2
923+
mode: rpmsign
924+
keyid: 2E20650941C6CCB2725FC377A2B637F535A86009
925+
privatekey: |
926+
-----BEGIN PGP PRIVATE KEY BLOCK-----
927+
928+
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
981+
oOuLM3lEWUfh
982+
=SRmt
983+
-----END PGP PRIVATE KEY BLOCK-----
984+
publickey: |
985+
-----BEGIN PGP PUBLIC KEY BLOCK-----
986+
987+
mQENBFuW9xABCACzCLYHwgGba7hi+lwhD/Hr5qqpg+UuN+88NclYgLWyl1nPpx2D
988+
JvH6p7ASj2P9BzEp0XatXLO4/uPQY2UX9UpWLT5wDGOdX4QCvZvFk4whcXHtcamr
989+
IQFTUjxRSIqvrq4t1h/4z635ztN0C6h5fWCxrCsoPJNQwEG/ZSDNXfwrJbsTIgus
990+
X037WXAzCYKzDZg9dGcUon4F2DHGGGqjOqLsyaGvOvOPddhorESuAJRe6Tl9ijzT
991+
NGc1uXIVEjEa5v9L4DJDqXYJqG35e0UuLkg0Wz4V9RVW/QP5DgnJAMQ8DUkXNHpa
992+
eD1H9Zg/EBt3/85BGCR7u7J6MYvhuVnLIXQ1ABEBAAG0K01vemlsbGEgQXV0b2dy
993+
YXBoIERldiA8bm9yZXBseUBleGFtcGxlLm5ldD6JAVQEEwEKAD4CGwMFCwkIBwMF
994+
FQoJCAsFFgIDAQACHgECF4AWIQSikQ5PvqB2AJvN5TbdCl2ZqqsfGgUCYGs5MgUJ
995+
KmxIIgAKCRDdCl2ZqqsfGuuYB/0b95Wk7JS6hy6d3HGQfiK0V1aFjTJydaCoEnB2
996+
/sT3Z4TvW+mUuAOVkGrpTi01s+difWpDcIE+mxKEOc7t2GB36U7uWV+7aNUMWPLo
997+
fhEAMQLVojonzKtR//9Iy9wqshMNEZ7ed7PA0Ju9DGpQJpUUWahmYHVbVpRtxJ92
998+
M5CJvHNy0HYx8JIM89en4r87kPi/cMsPHo7z1sHvv4MbRw7POpVIO5uYefxd5N4N
999+
HuenYmsfb4KhmVgMjiI6td5xF17tWLXjG3rcKKWRxmPhyKI1XLR5RRer0jLrd4Ge
1000+
rdGc96yQEPkQ6Th23V4imOSJBpJ/msSSBFWv5bb3mJdzabDvuQENBFuW9xABCADI
1001+
ykOe+xwyVVBNQsy+Bfzk5JsbjWmLcb/7sAy82TVSpq5LMm9v0OFlKMzpD6EjPGe8
1002+
wrk1OFHGXkLFhOvprpiZYxOnLbtcLbJ0mkgU3azdLsvBEFrDLv0N8AEdWBptkMCc
1003+
ar53W3iLKqxi9eKJCE86K6T7BtCR/NQ+SAUSz4Cv1mZacxMv8FZ5IllvsNmIFyoy
1004+
4mQx+tVS5OzNsd1D8gk93NlHQs82od4HI7BxUTnJgB0oZZz58MHCjjHIHCBp71RN
1005+
RFRufFArnrHsFkVxHFJH00Yn3WnJi4G5/IuZ8jUmBNFZ7JknwaHn5DX3XbF996MI
1006+
YVZtZtnQk0LdMQqVNs/rABEBAAGJATwEGAEKACYCGwwWIQSikQ5PvqB2AJvN5Tbd
1007+
Cl2ZqqsfGgUCYGs5DwUJKmxH/wAKCRDdCl2ZqqsfGiC5CAClOM2l9IRIl/iDCn+R
1008+
lMW1B12YP4/pPfco0KUMJwlb/lk0uybYjn3o9FbMS7qDlHQ8fn8SreaE4mdJA9Lq
1009+
hOahIUeDjfcem5/rPEWNfoiIQELsyvX2DZzjthHwh6+BCBKIekiBRDWj0AYeLtmv
1010+
M1vx6uxbvvIZsdOy7zU+jBFIGmL1GOwaaLuTv24EUwGCDq04j/fa5LaVuOSyCXnt
1011+
ask+6gjl1qr6zI44+IgZBySoJL86UxPUv4QVXCqgsvKoqMXggPpGEwGIIQec8mGC
1012+
Pl9w2LbeqzvlZonAvK/iLPHtaVcMg6fDa/OgKrYcGdfRIhX3u8uiyjLAoOuLM3lE
1013+
WUfh
1014+
=3Atg
1015+
-----END PGP PUBLIC KEY BLOCK-----
1016+
9211017
- id: pgpsubkey
9221018
# TEST 4096-bit RSA public / dev / signing subkey without master
9231019
type: gpg2
@@ -1675,6 +1771,7 @@ authorizations:
16751771
- testmarecdsa
16761772
- randompgp
16771773
- randompgp-debsign
1774+
- randompgp-rpmsign
16781775
- pgpsubkey
16791776
- pgpsubkey-debsign
16801777
- dummyrsapss

handlers_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -700,7 +700,7 @@ func TestDebug(t *testing.T) {
700700
func TestHandleGetAuthKeyIDs(t *testing.T) {
701701
ag, _ := newTestAutographer(t)
702702

703-
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\"]"
703+
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\"]"
704704

705705
var testcases = []HandlerTestCase{
706706
{

signer/gpg2/README.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
# PGP Signing with GPG2
22

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

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

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

12+
In `rpmsign` mode it accepts RPM files on the `/sign/files` interface and
13+
returns the signed RPM files with embedded GPG signatures.
14+
1215
Example Usage:
1316

1417
``` bash
@@ -62,8 +65,8 @@ signers:
6265
-----END PGP PUBLIC KEY BLOCK-----
6366
```
6467
65-
The **optional** field `mode` it can be either `gpg2` or
66-
`debsign`. When empty or missing it defaults to `gpg2` and should use
68+
The **optional** field `mode` can be `gpg2`, `debsign`, or `rpmsign`.
69+
When empty or missing it defaults to `gpg2` and should use
6770
the full key fingerprint in the `keyid` field. For example:
6871

6972
```yaml
@@ -100,14 +103,14 @@ This signer only supports the `/sign/data/` endpoint in `gpg2` mode:
100103
]
101104
```
102105

103-
This signer only supports the `/sign/files/` endpoint in `debsign` mode:
106+
This signer only supports the `/sign/files/` endpoint in both `debsign` and `rpmsign` mode:
104107

105108
``` json
106109
[
107110
{
108111
"input": "",
109112
"keyid": "pgpsubkey-debsign",
110-
"signed_files": [
113+
"files": [
111114
{
112115
"name": "sphinx_1.7.2-1.dsc",
113116
"content": "LS0tLS1CRUdJTiBQR1AgU0lHTkVEIE1FU1NBR0UtLS0tLQpIYXNoOiBTS..."
@@ -140,7 +143,7 @@ recover the standard armored signature that gnupg expects.
140143
]
141144
```
142145

143-
In `debsign` mode responses are at the field `signed_files`:
146+
In `debsign` and `rpmsign` mode responses are at the field `signed_files`:
144147

145148
```json
146149
[

signer/gpg2/gpg2.go

Lines changed: 82 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ const (
3131
// ModeDebsign represents a signer that signs files with debsign
3232
ModeDebsign = "debsign"
3333

34+
// ModeRPMSign represents a signer that signs RPM files with rpmsign
35+
ModeRPMSign = "rpmsign"
36+
3437
keyRingFilename = "autograph_gpg2_keyring.gpg"
3538
gpgConfFilename = "gpg.conf"
3639

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

102105
switch conf.Mode {
103106
case ModeDebsign:
107+
case ModeRPMSign:
104108
case ModeGPG2:
105109
case "": // default to signing in gpg2 mode to preserve backwards compat with old config files
106110
conf.Mode = ModeGPG2
107111
default:
108-
return nil, fmt.Errorf("gpg2: unknown signer mode %q, must be 'gpg2', or 'debsign'", conf.Mode)
112+
return nil, fmt.Errorf("gpg2: unknown signer mode %q, must be 'gpg2', 'debsign', or 'rpmsign'", conf.Mode)
109113
}
110114
s.Mode = conf.Mode
111115

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

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

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

@@ -384,7 +389,7 @@ func (s *GPG2Signer) SignFiles(inputs []signer.NamedUnsignedFile, options interf
384389
// of this directory.
385390
inputsTmpDir, err := os.MkdirTemp(s.tmpDir, "sign_files")
386391
if err != nil {
387-
err = fmt.Errorf("gpg2: error creating tempdir for debsign: %w", err)
392+
err = fmt.Errorf("gpg2: error creating tempdir for %s signing: %w", s.Mode, err)
388393
return
389394
}
390395
defer func() {
@@ -397,13 +402,20 @@ func (s *GPG2Signer) SignFiles(inputs []signer.NamedUnsignedFile, options interf
397402
var inputFilePaths []string
398403
for i, input := range inputs {
399404
ext := filepath.Ext(input.Name)
400-
if !(ext == ".buildinfo" || ext == ".dsc" || ext == ".changes") {
401-
return nil, fmt.Errorf("gpg2: cannot sign file %d. Files missing extension .buildinfo, .dsc, or .changes", i)
405+
switch s.Mode {
406+
case ModeDebsign:
407+
if !(ext == ".buildinfo" || ext == ".dsc" || ext == ".changes") {
408+
return nil, fmt.Errorf("gpg2: cannot sign file %d. File missing extension .buildinfo, .dsc, or .changes", i)
409+
}
410+
case ModeRPMSign:
411+
if ext != ".rpm" {
412+
return nil, fmt.Errorf("gpg2: cannot sign file %d. File missing extension .rpm", i)
413+
}
402414
}
403415
inputFilePath := filepath.Join(inputsTmpDir, input.Name)
404416
err := os.WriteFile(inputFilePath, input.Bytes, 0644)
405417
if err != nil {
406-
return nil, fmt.Errorf("gpg2: failed to write tempfile %d for debsign to sign: %w", i, err)
418+
return nil, fmt.Errorf("gpg2: failed to write tempfile %d for %s to sign: %w", i, s.Mode, err)
407419
}
408420
inputFilePaths = append(inputFilePaths, inputFilePath)
409421
}
@@ -412,50 +424,83 @@ func (s *GPG2Signer) SignFiles(inputs []signer.NamedUnsignedFile, options interf
412424
serializeSigning.Lock()
413425
defer serializeSigning.Unlock()
414426

415-
args := append([]string{
416-
// "Do not read any configuration files. This can only be used as the first option given on the command-line."
417-
"--no-conf",
418-
// "Specify the key ID to be used for signing; overrides any -m and -e options."
419-
// debsign prefers the pub key fingerprint: https://github.com/Debian/devscripts/blob/16f9a6d24f4bd564c315f81b89e08c3b4fb76f13/scripts/debsign.sh#L389
420-
"-k", s.KeyID,
421-
// "Recreate signature"
422-
"--re-sign",
423-
}, inputFilePaths...)
424-
debsignCmd := exec.Command("debsign", args...)
425-
debsignCmd.Env = append(os.Environ(),
427+
var cmd *exec.Cmd
428+
var passphraseRepeat int
429+
430+
switch s.Mode {
431+
case ModeDebsign:
432+
args := append([]string{
433+
// "Do not read any configuration files. This can only be used as the first option given on the command-line."
434+
"--no-conf",
435+
// "Specify the key ID to be used for signing; overrides any -m and -e options."
436+
// debsign prefers the pub key fingerprint: https://github.com/Debian/devscripts/blob/16f9a6d24f4bd564c315f81b89e08c3b4fb76f13/scripts/debsign.sh#L389
437+
"-k", s.KeyID,
438+
// "Recreate signature"
439+
"--re-sign",
440+
}, inputFilePaths...)
441+
cmd = exec.Command("debsign", args...)
442+
// debsign can call gpg multiple times per file
443+
passphraseRepeat = len(inputFilePaths) * 4
444+
case ModeRPMSign:
445+
// We pass passphrase-fd 3 via _gpg_sign_cmd_extra_args to override
446+
// passphrase-fd 0 from gpg.conf. This is because rpmsign uses stdin
447+
// to pipe header data to GPG for signing but not the passphrase. Having
448+
// passphrase-fd 0 in gpg.conf causes GPG to consume header data as passphrase,
449+
// corrupting the data getting signed since the start will be omitted.
450+
// We set up fd 3 ourselves via cmd.ExtraFiles and pass the passphrase through that.
451+
// See https://github.com/rpm-software-management/rpm/blob/4623d4601ee83b5e0ecd16dd3f54d2182b519b30/sign/rpmgensig.c#L254
452+
args := append([]string{
453+
"--addsign",
454+
"--key-id", s.KeyID,
455+
"--define", "__gpg /usr/bin/gpg",
456+
"--define", "_gpg_sign_cmd_extra_args --passphrase-fd 3",
457+
}, inputFilePaths...)
458+
cmd = exec.Command("rpmsign", args...)
459+
// rpmsign calls gpg once per file
460+
passphraseRepeat = len(inputFilePaths)
461+
}
462+
463+
cmd.Env = append(os.Environ(),
426464
fmt.Sprintf("GNUPGHOME=%s", s.tmpDir),
427465
)
428-
stdin, err := debsignCmd.StdinPipe()
466+
467+
passphraseReader, passphraseWriter, err := os.Pipe()
429468
if err != nil {
430-
return nil, fmt.Errorf("gpg2: failed to create stdin pipe for debsign cmd: %w", err)
469+
return nil, fmt.Errorf("gpg2: failed to create %s passphrase pipe: %w", s.Mode, err)
431470
}
471+
defer passphraseReader.Close()
432472

433-
// write passphrase multiple times to stdin
434-
// our gpg.conf prompts for the passphrase on each gpg call
435-
// and debsign can call gpg multiple times per file
436-
passphrasesForStdin := strings.Repeat(fmt.Sprintf("%s\n", s.passphrase), len(inputFilePaths)*4)
437-
if _, err = io.WriteString(stdin, passphrasesForStdin); err != nil {
438-
return nil, fmt.Errorf("gpg2: failed to write passphrase to stdin pipe for debsign cmd: %w", err)
473+
// Write passphrase to pipe (multiple times, once per signing operation)
474+
passphrases := strings.Repeat(fmt.Sprintf("%s\n", s.passphrase), passphraseRepeat)
475+
if _, err = io.WriteString(passphraseWriter, passphrases); err != nil {
476+
passphraseWriter.Close()
477+
return nil, fmt.Errorf("gpg2: failed to write %s passphrase to pipe: %w", s.Mode, err)
439478
}
440-
if err = stdin.Close(); err != nil {
441-
return nil, fmt.Errorf("gpg2: failed to close to stdin pipe for debsign cmd: %w", err)
479+
passphraseWriter.Close()
480+
481+
// For rpmsign, pass passphrase via fd 3, for debsign via fd 0 (stdin)
482+
if s.Mode == ModeRPMSign {
483+
cmd.ExtraFiles = []*os.File{passphraseReader}
484+
} else {
485+
cmd.Stdin = passphraseReader
442486
}
443-
out, err := debsignCmd.CombinedOutput()
487+
488+
out, err := cmd.CombinedOutput()
444489
if err != nil {
445-
return nil, fmt.Errorf("gpg2: failed to debsign inputs %s\n%s", err, out)
490+
return nil, fmt.Errorf("gpg2: failed to %s inputs: %w\n%s", s.Mode, err, out)
446491
}
447492

448493
// read the signed tempfiles
449494
for i, inputFilePath := range inputFilePaths {
450495
signedFileBytes, err := os.ReadFile(inputFilePath)
451496
if err != nil {
452-
return nil, fmt.Errorf("gpg2: failed to read %d %q signed by debsign: %w", i, inputFilePath, err)
497+
return nil, fmt.Errorf("gpg2: failed to read %d %q signed by %s: %w", i, inputFilePath, s.Mode, err)
453498
}
454499
signedFiles = append(signedFiles, signer.NamedSignedFile{
455500
Name: inputs[i].Name,
456501
Bytes: signedFileBytes,
457502
})
458503
}
459-
log.Debugf("debsign output:\n%s\n", string(out))
504+
log.Debugf("%s output:\n%s\n", s.Mode, string(out))
460505
return signedFiles, nil
461506
}

0 commit comments

Comments
 (0)