1
1
mirror of https://github.com/goreleaser/nfpm synced 2025-04-20 12:38:04 +02:00
nfpm/internal/sign/rsa.go
Yaroslav 1667ef9585
feat: support PKCS8 keys for APK signing (#804)
* support PKCS8 keys for APK signing

Call correct parsing function for given RSA key depending on its PEM
header. So we can use both PKCS1 and PKCS8 keys to sign APK files.

Fixes https://github.com/goreleaser/nfpm/issues/799

* fix golangci-lint warnings
2024-03-27 10:28:44 -03:00

145 lines
3.6 KiB
Go

package sign
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha1" // nolint:gosec
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io"
"os"
)
var (
errNoPemBlock = errors.New("no PEM block found")
errDigestNotSH1 = errors.New("digest is not a SHA1 hash")
errNoPassphrase = errors.New("key is encrypted but no passphrase was provided")
errNoRSAKey = errors.New("key is not an RSA key")
)
const (
PKCS1PrivkeyPreamble = "RSA PRIVATE KEY"
PKCS8PrivkeyPreamble = "PRIVATE KEY"
)
// RSASignSHA1Digest signs the provided SHA1 message digest. The key file
// must be in the PEM format and can either be encrypted or not.
func RSASignSHA1Digest(sha1Digest []byte, keyFile, passphrase string) ([]byte, error) {
if len(sha1Digest) != sha1.Size {
return nil, errDigestNotSH1
}
keyFileContent, err := os.ReadFile(keyFile)
if err != nil {
return nil, fmt.Errorf("reading key file: %w", err)
}
block, _ := pem.Decode(keyFileContent)
if block == nil {
return nil, errNoPemBlock
}
blockData := block.Bytes
if x509.IsEncryptedPEMBlock(block) { //nolint:staticcheck
if passphrase == "" {
return nil, errNoPassphrase
}
var decryptedBlockData []byte
decryptedBlockData, err = x509.DecryptPEMBlock(block, []byte(passphrase)) //nolint:staticcheck
if err != nil {
return nil, fmt.Errorf("decrypt private key PEM block: %w", err)
}
blockData = decryptedBlockData
}
var priv crypto.Signer
switch block.Type {
case PKCS1PrivkeyPreamble:
priv, err = x509.ParsePKCS1PrivateKey(blockData)
if err != nil {
return nil, fmt.Errorf("parse PKCS#1 private key: %w", err)
}
case PKCS8PrivkeyPreamble:
privAny, err := x509.ParsePKCS8PrivateKey(blockData)
if err != nil {
return nil, fmt.Errorf("parse PKCS#8 private key: %w", err)
}
privTmp, ok := privAny.(crypto.Signer)
if !ok {
return nil, fmt.Errorf("cannot sign with given private key")
}
priv = privTmp
default:
return nil, fmt.Errorf(`key type "%v" is not supported`, block.Type)
}
signature, err := priv.Sign(rand.Reader, sha1Digest, crypto.SHA1)
if err != nil {
return nil, fmt.Errorf("signing: %w", err)
}
return signature, nil
}
func rsaSign(message io.Reader, keyFile, passphrase string) ([]byte, error) {
sha1Hash := sha1.New() // nolint:gosec
_, err := io.Copy(sha1Hash, message)
if err != nil {
return nil, fmt.Errorf("create SHA1 message digest: %w", err)
}
return RSASignSHA1Digest(sha1Hash.Sum(nil), keyFile, passphrase)
}
// RSAVerifySHA1Digest is exported for use in tests and verifies a signature over the
// provided SHA1 hash of a message. The key file must be in the PEM format.
func RSAVerifySHA1Digest(sha1Digest, signature []byte, publicKeyFile string) error {
if len(sha1Digest) != sha1.Size {
return errDigestNotSH1
}
keyFileContent, err := os.ReadFile(publicKeyFile)
if err != nil {
return fmt.Errorf("reading key file: %w", err)
}
block, _ := pem.Decode(keyFileContent)
if block == nil {
return errNoPemBlock
}
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return fmt.Errorf("parse PKIX public key: %w", err)
}
rsaPub, ok := pub.(*rsa.PublicKey)
if !ok {
return errNoRSAKey
}
err = rsa.VerifyPKCS1v15(rsaPub, crypto.SHA1, sha1Digest, signature)
if err != nil {
return fmt.Errorf("verify PKCS1v15 signature: %w", err)
}
return nil
}
func rsaVerify(message io.Reader, signature []byte, publicKeyFile string) error {
sha1Hash := sha1.New() // nolint:gosec
_, err := io.Copy(sha1Hash, message)
if err != nil {
return fmt.Errorf("create SHA1 message digest: %w", err)
}
return RSAVerifySHA1Digest(sha1Hash.Sum(nil), signature, publicKeyFile)
}