blob: 0b2bbbf36aa9ec07f4de8eb3eb74de6d1029582b [file] [log] [blame] [edit]
package dkms
import (
"bytes"
"context"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/binary"
"encoding/pem"
"fmt"
"hash/crc32"
"math/big"
"os"
"strings"
kms "cloud.google.com/go/kms/apiv1"
"cloud.google.com/go/kms/apiv1/kmspb"
"github.com/golang/glog"
"google.golang.org/protobuf/types/known/wrapperspb"
)
const (
// PKEYIDPKCS7 is the constant PKEY_ID_PKCS7 defined in https://github.com/torvalds/linux/blob/master/scripts/sign-file.c
PKEYIDPKCS7 = byte(2)
// magicNumber is the constant magic_number defined in https://github.com/torvalds/linux/blob/master/scripts/sign-file.c
magicNumber = "~Module signature appended~\n"
)
// https://www.rfc-editor.org/rfc/rfc2315#section-9.1
// SignedData ::= SEQUENCE {
// version Version,
// digestAlgorithms DigestAlgorithmIdentifiers,
// contentInfo ContentInfo,
// certificates
// [0] IMPLICIT ExtendedCertificatesAndCertificates
// OPTIONAL,
// crls
// [1] IMPLICIT CertificateRevocationLists OPTIONAL,
// signerInfos SignerInfos }
type signedData struct {
Version int
DigestAlgorithmIdentifiers []pkix.AlgorithmIdentifier `asn1:"set"`
ContentInfo contentInfo
SignerInfos []signerInfo `asn1:"set"`
}
// https://www.rfc-editor.org/rfc/rfc2315#section-7
// ContentInfo ::= SEQUENCE {
//
// contentType ContentType,
// content // NOTE: not needed for detached signature
// [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL }
//
// ContentType ::= OBJECT IDENTIFIER
type contentInfo struct {
ContentType asn1.ObjectIdentifier
}
// https://www.rfc-editor.org/rfc/rfc2315#section-9.2
// SignerInfo ::= SEQUENCE {
//
// version Version,
// issuerAndSerialNumber IssuerAndSerialNumber,
// digestAlgorithm DigestAlgorithmIdentifier,
// authenticatedAttributes // NOTE: not used in Linux kernel module signature
// [0] IMPLICIT Attributes OPTIONAL,
// digestEncryptionAlgorithm
// DigestEncryptionAlgorithmIdentifier,
// encryptedDigest EncryptedDigest,
// unauthenticatedAttributes // NOTE: not used in Linux kernel module signature
// [1] IMPLICIT Attributes OPTIONAL }
//
// EncryptedDigest ::= OCTET STRING
type signerInfo struct {
Version int
IssuerAndSerialNumber issuerAndSerialNumber
DigestAlgorithm pkix.AlgorithmIdentifier
DigestEncryptionAlgorithm pkix.AlgorithmIdentifier
EncryptedDigest []byte
}
// https://www.rfc-editor.org/rfc/rfc2315#section-6.7
// IssuerAndSerialNumber ::= SEQUENCE {
//
// issuer Name,
// serialNumber CertificateSerialNumber }
type issuerAndSerialNumber struct {
Issuer asn1.RawValue
SerialNumber *big.Int
}
type pkcs7Blob struct {
Oid asn1.ObjectIdentifier
SignedData signedData `asn1:"tag:0,explicit"`
}
type digestAlgorithm struct {
identifier asn1.ObjectIdentifier
hash crypto.Hash
}
// ModuleSigner is an interface for signing kernel modules.
type ModuleSigner interface {
// Sign generates a digital signature for the given module bytes.
Sign(moduleBytes []byte, hash crypto.Hash) ([]byte, error)
// retrieveDigestEncryptionAlgorithm returns the algorithm identifier used for
// encrypting the digest (e.g., RSA).
retrieveDigestEncryptionAlgorithm() (*pkix.AlgorithmIdentifier, error)
}
// KmsSigner implements the ModuleSigner interface using Google Cloud KMS for signing operations.
type KmsSigner struct {
ctx context.Context
client *kms.KeyManagementClient
keyName string
}
// LocalSigner implements the ModuleSigner interface using a local private key for signing.
type LocalSigner struct {
key crypto.PrivateKey
}
var (
oidData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1}
oidRsaEncryption = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}
oidSignedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2}
digests = map[string]digestAlgorithm{
"sha256": {
identifier: asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1},
hash: crypto.SHA256,
},
"sha384": {
identifier: asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2},
hash: crypto.SHA384,
},
"sha512": {
identifier: asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 3},
hash: crypto.SHA512,
},
"sha3-256": {
identifier: asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 8},
hash: crypto.SHA3_256,
},
"sha3-384": {
identifier: asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 9},
hash: crypto.SHA3_384,
},
"sha3-512": {
identifier: asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 10},
hash: crypto.SHA3_512,
},
}
)
func newKmsSigner(ctx context.Context, kmsKey string) (*KmsSigner, error) {
client, err := kms.NewKeyManagementClient(ctx)
if err != nil {
return nil, err
}
return &KmsSigner{ctx: ctx, client: client, keyName: kmsKey}, nil
}
func newLocalSigner(keyPath string) (*LocalSigner, error) {
keyBytes, err := os.ReadFile(keyPath)
if err != nil {
return nil, fmt.Errorf("failed to read private key file (%s): %v", keyPath, err)
}
key, err := parsePrivateKey(keyBytes)
if err != nil {
return nil, fmt.Errorf("failed to retrieve private key: %v", err)
}
return &LocalSigner{key: key}, nil
}
func (l *LocalSigner) Sign(moduleBytes []byte, hash crypto.Hash) ([]byte, error) {
// Retrieve the crypto signer if it is implemented for the private key
signer, ok := l.key.(crypto.Signer)
if !ok {
return nil, fmt.Errorf("could not retrieve crypto signer for key type: %T", l.key)
}
h := hash.New()
h.Write(moduleBytes)
moduleDigest := h.Sum(nil)
signature, err := signer.Sign(rand.Reader, moduleDigest, hash)
if err != nil {
return nil, fmt.Errorf("failed to sign the module digest: %v", err)
}
return signature, nil
}
func (l *LocalSigner) retrieveDigestEncryptionAlgorithm() (*pkix.AlgorithmIdentifier, error) {
switch l.key.(type) {
case *rsa.PrivateKey:
return &pkix.AlgorithmIdentifier{Algorithm: oidRsaEncryption, Parameters: asn1.NullRawValue}, nil
default:
return nil, fmt.Errorf("unsupported digest encryption algorithm: %T", l.key)
}
}
func (k *KmsSigner) Sign(moduleBytes []byte, hash crypto.Hash) ([]byte, error) {
h := hash.New()
h.Write(moduleBytes)
moduleDigest := h.Sum(nil)
kmsDigest, err := k.retrieveDigestAlg(hash, moduleDigest)
if err != nil {
return nil, fmt.Errorf("failed to retrieve KMS digest algorithm: %v", err)
}
// Compute digest's CRC32C
t := crc32.MakeTable(crc32.Castagnoli)
digestCRC32C := crc32.Checksum(moduleDigest, t)
req := &kmspb.AsymmetricSignRequest{
Name: k.keyName,
Digest: kmsDigest,
DigestCrc32C: wrapperspb.Int64(int64(digestCRC32C)),
}
result, err := k.client.AsymmetricSign(k.ctx, req)
if err != nil {
return nil, fmt.Errorf("failed to generate signature using KMS: %v", err)
}
// Verify integrity of the result
if !result.VerifiedDigestCrc32C {
return nil, fmt.Errorf("AsymmetricSignError: digest corrupted in transit")
}
if result.Name != req.Name {
return nil, fmt.Errorf("AsymmetricSignError: mismatched key name in the response")
}
if int64(crc32.Checksum(result.Signature, t)) != result.SignatureCrc32C.Value {
return nil, fmt.Errorf("AsymmetricSignError: response corrupted in transit")
}
return result.Signature, nil
}
func (k *KmsSigner) retrieveDigestEncryptionAlgorithm() (*pkix.AlgorithmIdentifier, error) {
req := &kmspb.GetCryptoKeyVersionRequest{
Name: k.keyName,
}
keyVersion, err := k.client.GetCryptoKeyVersion(k.ctx, req)
if err != nil {
return nil, fmt.Errorf("failed to get key version: %v", err)
}
// Verify that key encryption is supported
encryptAlg := keyVersion.GetAlgorithm().String()
if strings.HasPrefix(encryptAlg, "RSA_") {
return &pkix.AlgorithmIdentifier{Algorithm: oidRsaEncryption, Parameters: asn1.NullRawValue}, nil
}
return nil, fmt.Errorf("unsupported digest encryption algorithm: %v", encryptAlg)
}
func (k *KmsSigner) retrieveDigestAlg(hash crypto.Hash, moduleDigest []byte) (*kmspb.Digest, error) {
var digest *kmspb.Digest
switch hash {
case crypto.SHA256:
digest = &kmspb.Digest{
Digest: &kmspb.Digest_Sha256{
Sha256: moduleDigest,
},
}
case crypto.SHA384:
digest = &kmspb.Digest{
Digest: &kmspb.Digest_Sha384{
Sha384: moduleDigest,
},
}
case crypto.SHA512:
digest = &kmspb.Digest{
Digest: &kmspb.Digest_Sha512{
Sha512: moduleDigest,
},
}
default:
return nil, fmt.Errorf("unsupported kms digest algorithm: %s", hash.String())
}
return digest, nil
}
// SignModules signs a list of compiled kernel modules using the provided ModuleSigner.
// Currently, only rsa private keys are supported. It creates a pkcs7 signature
// and appends it to the end of a compiled kernel module.
//
// The ModuleSigner handles the specific signing method (e.g., local key or Cloud KMS)
// This is an implementation of https://github.com/torvalds/linux/blob/master/scripts/sign-file.c
func SignModules(modules []Module, certificatePath, hash string, signer ModuleSigner) error {
certBytes, err := os.ReadFile(certificatePath)
if err != nil {
return fmt.Errorf("failed to read certificate key file (%s): %v", certificatePath, err)
}
cert, err := x509.ParseCertificate(certBytes)
if err != nil {
return fmt.Errorf("failed to retrieve certificate (%s): %v", certificatePath, err)
}
digest, ok := digests[strings.ToLower(hash)]
if !ok {
return fmt.Errorf("unsupported digest algorithm: %s", hash)
}
encryptAlg, err := signer.retrieveDigestEncryptionAlgorithm()
if err != nil {
return fmt.Errorf("failed to retrieve digest encryption algorithm: %v", err)
}
var signedModules, skippedModules []string
for _, module := range modules {
moduleName := module.BuiltName
modulePath := module.BuiltPath()
glog.Infof("signing module %s", moduleName)
moduleBytes, err := os.ReadFile(modulePath)
if err != nil {
skippedModules = append(skippedModules, moduleName)
glog.Errorf("failed to read module file (%s) [skipping]: %v", moduleName, err)
continue
}
signedBytes, err := signModule(signer, moduleBytes, digest, cert, encryptAlg)
if err != nil {
skippedModules = append(skippedModules, moduleName)
glog.Errorf("failed to sign module (%s) [skipping]: %v", moduleName, err)
continue
}
if err := os.WriteFile(modulePath, signedBytes, 0644); err != nil {
skippedModules = append(skippedModules, moduleName)
glog.Errorf("failed to write signed module (%s) to file (%s) [skipping]: %v", moduleName, modulePath, err)
continue
}
signedModules = append(signedModules, moduleName)
glog.Infof("successfully signed module '%s'", moduleName)
}
if len(signedModules) > 0 {
glog.Infof("Successfully signed module(s): %v", signedModules)
}
if len(skippedModules) > 0 {
glog.Errorf("Failed to sign module(s): %v", skippedModules)
}
return nil
}
func parsePrivateKey(contents []byte) (crypto.PrivateKey, error) {
// Retrieve pem encoded block.
block, _ := pem.Decode(contents)
if block == nil {
return nil, fmt.Errorf("failed to decode pem-formatted key or invalid key type")
}
var privateKey crypto.PrivateKey
var err error
switch block.Type {
case "RSA PRIVATE KEY":
privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse rsa private key: %v", err)
}
case "PRIVATE KEY":
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse private key: %v", err)
}
switch k := key.(type) {
case *rsa.PrivateKey:
privateKey = k
default:
return nil, fmt.Errorf("unsupported key type: %T", key)
}
default:
return nil, fmt.Errorf("unsupported block type: %s", block.Type)
}
return privateKey, nil
}
func signModule(signer ModuleSigner, moduleBytes []byte, digest digestAlgorithm, cert *x509.Certificate, encryptAlg *pkix.AlgorithmIdentifier) ([]byte, error) {
signature, err := signer.Sign(moduleBytes, digest.hash)
if err != nil {
return nil, fmt.Errorf("failed to generate module signature: %v", err)
}
digestAlg := &pkix.AlgorithmIdentifier{Algorithm: digest.identifier, Parameters: asn1.NullRawValue}
sinfo := signerInfo{
Version: 1,
IssuerAndSerialNumber: issuerAndSerialNumber{
Issuer: asn1.RawValue{FullBytes: cert.RawIssuer},
SerialNumber: cert.SerialNumber,
},
DigestAlgorithm: *digestAlg,
DigestEncryptionAlgorithm: *encryptAlg,
EncryptedDigest: signature,
}
blob := pkcs7Blob{
Oid: oidSignedData,
SignedData: signedData{
Version: 1,
DigestAlgorithmIdentifiers: []pkix.AlgorithmIdentifier{*digestAlg},
ContentInfo: contentInfo{ContentType: oidData},
SignerInfos: []signerInfo{sinfo},
},
}
marshalled, err := asn1.Marshal(blob)
if err != nil {
return nil, fmt.Errorf("failed to marshal signed module pkcs7 blob: %v", err)
}
return appendSignature(marshalled, moduleBytes)
}
// appendSignature appends a raw PKCS#7 signature to the end of a given kernel module.
func appendSignature(signature []byte, moduleBytes []byte) ([]byte, error) {
var buf bytes.Buffer
// Write bytes of kernel module.
if _, err := buf.Write(moduleBytes); err != nil {
return nil, fmt.Errorf("failed to write module bytes to buffer: %v", err)
}
// Copy bytes of module signature.
sigSize, err := buf.Write(signature)
if err != nil {
return nil, fmt.Errorf("failed to write signature to buffer: %v", err)
}
// Append the marker and the PKCS#7 message.
// signatureInfo is the struct module_signature defined in
// https://github.com/torvalds/linux/blob/master/scripts/sign-file.c
signatureInfo := [12]byte{}
// signatureInfo[2] is the id_type of struct module_signature
signatureInfo[2] = PKEYIDPKCS7
// signatureInfo[8:12] is the sig_len of struct module_signature.
// Using BigEndian as the sig_len should be in network byte order.
binary.BigEndian.PutUint32(signatureInfo[8:12], uint32(sigSize))
if _, err := buf.Write(signatureInfo[:]); err != nil {
return nil, fmt.Errorf("failed to write module signature struct to buffer: %v", err)
}
if _, err := buf.WriteString(magicNumber); err != nil {
return nil, fmt.Errorf("failed to write magic number to buffer: %v", err)
}
return buf.Bytes(), nil
}