mirror of
https://github.com/fleetdm/fleet
synced 2026-05-06 14:58:33 +00:00
For #30473 This change adds a vendored `httpsig-go` library to our repo. We cannot use the upstream library because it has not merged the change we need: https://github.com/remitly-oss/httpsig-go/pull/25 Thus, we need our own copy at this point. The instructions for keeping this library up to date (if needed) are in `UPDATE_INSTRUCTIONS`. None of the coderabbitai review comments are relevant to the code/features we are going to use for HTTP message signatures. We will use this library in subsequent PRs for the TPM-backed HTTP message signature feature. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Introduced a Go library for HTTP message signing and verification, supporting multiple cryptographic algorithms (RSA, ECDSA, Ed25519, HMAC). * Added utilities for key management, including JWK and PEM key handling. * Provided HTTP client and server helpers for automatic request signing and signature verification. * Implemented structured error handling and metadata extraction for signatures. * **Documentation** * Added comprehensive README, usage examples, and update instructions. * Included license and configuration files for third-party and testing tools. * **Tests** * Added extensive unit, integration, and fuzz tests covering signing, verification, and key handling. * Included official RFC test vectors and various test data files for robust validation. * **Chores** * Integrated continuous integration workflows and ignore files for code quality and security analysis. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
143 lines
4 KiB
Go
143 lines
4 KiB
Go
/*
|
|
Package keyutil provides some basic PEM and JWK key handling without dependencies. It is not meant as a replacement for a full key handling library.
|
|
*/
|
|
package keyutil
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"os"
|
|
)
|
|
|
|
var (
|
|
oidPublicKeyRSAPSS = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10}
|
|
)
|
|
|
|
// MustReadPublicKeyFile reads a PEM encoded public key file or panics
|
|
func MustReadPublicKeyFile(pubkeyFile string) crypto.PublicKey {
|
|
pk, err := ReadPublicKeyFile(pubkeyFile)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return pk
|
|
}
|
|
|
|
// ReadPublicKeyFile reads a PEM encdoded public key file and parses into crypto.PublicKey
|
|
func ReadPublicKeyFile(pubkeyFile string) (crypto.PublicKey, error) {
|
|
keyBytes, err := os.ReadFile(pubkeyFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to read public key file '%s': %w", pubkeyFile, err)
|
|
}
|
|
return ReadPublicKey(keyBytes)
|
|
}
|
|
|
|
// ReadPublicKey decodes a PEM encoded public key and parses into crypto.PublicKey
|
|
func ReadPublicKey(encodedPubkey []byte) (crypto.PublicKey, error) {
|
|
block, _ := pem.Decode(encodedPubkey)
|
|
if block == nil {
|
|
return nil, fmt.Errorf("Failed to PEM decode public key")
|
|
}
|
|
var key crypto.PublicKey
|
|
var err error
|
|
|
|
switch block.Type {
|
|
case "PUBLIC KEY":
|
|
key, err = x509.ParsePKIXPublicKey(block.Bytes)
|
|
case "RSA PUBLIC KEY":
|
|
key, err = x509.ParsePKCS1PublicKey(block.Bytes)
|
|
default:
|
|
return nil, fmt.Errorf("Unsupported pubkey format '%s'", block.Type)
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to parse public key with format '%s': %w", block.Type, err)
|
|
}
|
|
|
|
return key, nil
|
|
}
|
|
|
|
// MustReadPrivateKeyFile decodes a PEM encoded private key file and parses into a crypto.PrivateKey or panics.
|
|
func MustReadPrivateKeyFile(pkFile string) crypto.PrivateKey {
|
|
pk, err := ReadPrivateKeyFile(pkFile)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return pk
|
|
}
|
|
|
|
func MustReadPrivateKey(encodedPrivateKey []byte) crypto.PrivateKey {
|
|
pkey, err := ReadPrivateKey(encodedPrivateKey)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return pkey
|
|
}
|
|
|
|
// ReadPrivateKeyFile opens the given file and calls ReadPrivateKey to return a crypto.PrivateKey
|
|
func ReadPrivateKeyFile(pkFile string) (crypto.PrivateKey, error) {
|
|
keyBytes, err := os.ReadFile(pkFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to read private key file '%s': %w", pkFile, err)
|
|
}
|
|
return ReadPrivateKey(keyBytes)
|
|
}
|
|
|
|
// ReadPrivateKey decoded a PEM encoded private key and parses into a crypto.PrivateKey.
|
|
func ReadPrivateKey(encodedPrivateKey []byte) (crypto.PrivateKey, error) {
|
|
block, _ := pem.Decode(encodedPrivateKey)
|
|
|
|
if block == nil {
|
|
return nil, fmt.Errorf("Failed to PEM decode private key")
|
|
}
|
|
|
|
var key crypto.PrivateKey
|
|
var err error
|
|
|
|
switch block.Type {
|
|
case "PRIVATE KEY":
|
|
key, err = x509.ParsePKCS8PrivateKey(block.Bytes)
|
|
if err != nil {
|
|
// Try to handle RSAPSS
|
|
psskey, psserr := parseRSAPSS(block)
|
|
if psserr == nil {
|
|
// success
|
|
key = psskey
|
|
err = psserr
|
|
}
|
|
}
|
|
case "EC PRIVATE KEY":
|
|
key, err = x509.ParseECPrivateKey(block.Bytes)
|
|
case "RSA PRIVATE KEY":
|
|
key, err = x509.ParsePKCS1PrivateKey(block.Bytes)
|
|
default:
|
|
return nil, fmt.Errorf("Unsupported private key format '%s'", block.Type)
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to parse private key with format '%s': %w", block.Type, err)
|
|
}
|
|
return key, nil
|
|
}
|
|
|
|
func parseRSAPSS(block *pem.Block) (crypto.PrivateKey, error) {
|
|
// The rsa-pss key is PKCS8 encoded but the golang 1.19 parser doesn't recognize the algorithm and gives 'PKCS#8 wrapping contained private key with unknown algorithm: 1.2.840.113549.1.1.10
|
|
|
|
// Instead do the asn1 unmarshaling and check here.
|
|
pkcs8 := struct {
|
|
Version int
|
|
Algo pkix.AlgorithmIdentifier
|
|
PrivateKey []byte
|
|
}{}
|
|
|
|
_, err := asn1.Unmarshal(block.Bytes, &pkcs8)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to ans1 unmarshal private key: %w", err)
|
|
}
|
|
|
|
if !pkcs8.Algo.Algorithm.Equal(oidPublicKeyRSAPSS) {
|
|
return nil, fmt.Errorf("PKCS#8 wrapping contained private key with unknown algorithm: %s", pkcs8.Algo.Algorithm)
|
|
}
|
|
return x509.ParsePKCS1PrivateKey(pkcs8.PrivateKey)
|
|
}
|