9d5f021792
Bad news: the hot patch we were using breaks in Go 1.19.4: 6109c07ec4
Good news: we can now patch with an environment variable at runtime.
Co-authored-by: Christopher Swenson <christopher.swenson@hashicorp.com>
436 lines
14 KiB
Go
436 lines
14 KiB
Go
package pkcs7
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto"
|
|
"crypto/dsa"
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"time"
|
|
|
|
"github.com/hashicorp/vault/internal"
|
|
)
|
|
|
|
func init() {
|
|
internal.PatchSha1()
|
|
}
|
|
|
|
// SignedData is an opaque data structure for creating signed data payloads
|
|
type SignedData struct {
|
|
sd signedData
|
|
certs []*x509.Certificate
|
|
data, messageDigest []byte
|
|
digestOid asn1.ObjectIdentifier
|
|
encryptionOid asn1.ObjectIdentifier
|
|
}
|
|
|
|
// NewSignedData takes data and initializes a PKCS7 SignedData struct that is
|
|
// ready to be signed via AddSigner. The digest algorithm is set to SHA-256 by default
|
|
// and can be changed by calling SetDigestAlgorithm.
|
|
func NewSignedData(data []byte) (*SignedData, error) {
|
|
content, err := asn1.Marshal(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ci := contentInfo{
|
|
ContentType: OIDData,
|
|
Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: content, IsCompound: true},
|
|
}
|
|
sd := signedData{
|
|
ContentInfo: ci,
|
|
Version: 1,
|
|
}
|
|
return &SignedData{sd: sd, data: data, digestOid: OIDDigestAlgorithmSHA256}, nil
|
|
}
|
|
|
|
// SignerInfoConfig are optional values to include when adding a signer
|
|
type SignerInfoConfig struct {
|
|
ExtraSignedAttributes []Attribute
|
|
ExtraUnsignedAttributes []Attribute
|
|
}
|
|
|
|
type signedData struct {
|
|
Version int `asn1:"default:1"`
|
|
DigestAlgorithmIdentifiers []pkix.AlgorithmIdentifier `asn1:"set"`
|
|
ContentInfo contentInfo
|
|
Certificates rawCertificates `asn1:"optional,tag:0"`
|
|
CRLs []pkix.CertificateList `asn1:"optional,tag:1"`
|
|
SignerInfos []signerInfo `asn1:"set"`
|
|
}
|
|
|
|
type signerInfo struct {
|
|
Version int `asn1:"default:1"`
|
|
IssuerAndSerialNumber issuerAndSerial
|
|
DigestAlgorithm pkix.AlgorithmIdentifier
|
|
AuthenticatedAttributes []attribute `asn1:"optional,omitempty,tag:0"`
|
|
DigestEncryptionAlgorithm pkix.AlgorithmIdentifier
|
|
EncryptedDigest []byte
|
|
UnauthenticatedAttributes []attribute `asn1:"optional,omitempty,tag:1"`
|
|
}
|
|
|
|
type attribute struct {
|
|
Type asn1.ObjectIdentifier
|
|
Value asn1.RawValue `asn1:"set"`
|
|
}
|
|
|
|
func marshalAttributes(attrs []attribute) ([]byte, error) {
|
|
encodedAttributes, err := asn1.Marshal(struct {
|
|
A []attribute `asn1:"set"`
|
|
}{A: attrs})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Remove the leading sequence octets
|
|
var raw asn1.RawValue
|
|
asn1.Unmarshal(encodedAttributes, &raw)
|
|
return raw.Bytes, nil
|
|
}
|
|
|
|
type rawCertificates struct {
|
|
Raw asn1.RawContent
|
|
}
|
|
|
|
type issuerAndSerial struct {
|
|
IssuerName asn1.RawValue
|
|
SerialNumber *big.Int
|
|
}
|
|
|
|
// SetDigestAlgorithm sets the digest algorithm to be used in the signing process.
|
|
//
|
|
// This should be called before adding signers
|
|
func (sd *SignedData) SetDigestAlgorithm(d asn1.ObjectIdentifier) {
|
|
sd.digestOid = d
|
|
}
|
|
|
|
// SetEncryptionAlgorithm sets the encryption algorithm to be used in the signing process.
|
|
//
|
|
// This should be called before adding signers
|
|
func (sd *SignedData) SetEncryptionAlgorithm(d asn1.ObjectIdentifier) {
|
|
sd.encryptionOid = d
|
|
}
|
|
|
|
// AddSigner is a wrapper around AddSignerChain() that adds a signer without any parent.
|
|
func (sd *SignedData) AddSigner(ee *x509.Certificate, pkey crypto.PrivateKey, config SignerInfoConfig) error {
|
|
var parents []*x509.Certificate
|
|
return sd.AddSignerChain(ee, pkey, parents, config)
|
|
}
|
|
|
|
// AddSignerChain signs attributes about the content and adds certificates
|
|
// and signers infos to the Signed Data. The certificate and private key
|
|
// of the end-entity signer are used to issue the signature, and any
|
|
// parent of that end-entity that need to be added to the list of
|
|
// certifications can be specified in the parents slice.
|
|
//
|
|
// The signature algorithm used to hash the data is the one of the end-entity
|
|
// certificate.
|
|
func (sd *SignedData) AddSignerChain(ee *x509.Certificate, pkey crypto.PrivateKey, parents []*x509.Certificate, config SignerInfoConfig) error {
|
|
// Following RFC 2315, 9.2 SignerInfo type, the distinguished name of
|
|
// the issuer of the end-entity signer is stored in the issuerAndSerialNumber
|
|
// section of the SignedData.SignerInfo, alongside the serial number of
|
|
// the end-entity.
|
|
var ias issuerAndSerial
|
|
ias.SerialNumber = ee.SerialNumber
|
|
if len(parents) == 0 {
|
|
// no parent, the issuer is the end-entity cert itself
|
|
ias.IssuerName = asn1.RawValue{FullBytes: ee.RawIssuer}
|
|
} else {
|
|
err := verifyPartialChain(ee, parents)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// the first parent is the issuer
|
|
ias.IssuerName = asn1.RawValue{FullBytes: parents[0].RawSubject}
|
|
}
|
|
sd.sd.DigestAlgorithmIdentifiers = append(sd.sd.DigestAlgorithmIdentifiers,
|
|
pkix.AlgorithmIdentifier{Algorithm: sd.digestOid},
|
|
)
|
|
hash, err := getHashForOID(sd.digestOid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
h := hash.New()
|
|
h.Write(sd.data)
|
|
sd.messageDigest = h.Sum(nil)
|
|
encryptionOid, err := getOIDForEncryptionAlgorithm(pkey, sd.digestOid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
attrs := &attributes{}
|
|
attrs.Add(OIDAttributeContentType, sd.sd.ContentInfo.ContentType)
|
|
attrs.Add(OIDAttributeMessageDigest, sd.messageDigest)
|
|
attrs.Add(OIDAttributeSigningTime, time.Now().UTC())
|
|
for _, attr := range config.ExtraSignedAttributes {
|
|
attrs.Add(attr.Type, attr.Value)
|
|
}
|
|
finalAttrs, err := attrs.ForMarshalling()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
unsignedAttrs := &attributes{}
|
|
for _, attr := range config.ExtraUnsignedAttributes {
|
|
unsignedAttrs.Add(attr.Type, attr.Value)
|
|
}
|
|
finalUnsignedAttrs, err := unsignedAttrs.ForMarshalling()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// create signature of signed attributes
|
|
signature, err := signAttributes(finalAttrs, pkey, hash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
signer := signerInfo{
|
|
AuthenticatedAttributes: finalAttrs,
|
|
UnauthenticatedAttributes: finalUnsignedAttrs,
|
|
DigestAlgorithm: pkix.AlgorithmIdentifier{Algorithm: sd.digestOid},
|
|
DigestEncryptionAlgorithm: pkix.AlgorithmIdentifier{Algorithm: encryptionOid},
|
|
IssuerAndSerialNumber: ias,
|
|
EncryptedDigest: signature,
|
|
Version: 1,
|
|
}
|
|
sd.certs = append(sd.certs, ee)
|
|
if len(parents) > 0 {
|
|
sd.certs = append(sd.certs, parents...)
|
|
}
|
|
sd.sd.SignerInfos = append(sd.sd.SignerInfos, signer)
|
|
return nil
|
|
}
|
|
|
|
// SignWithoutAttr issues a signature on the content of the pkcs7 SignedData.
|
|
// Unlike AddSigner/AddSignerChain, it calculates the digest on the data alone
|
|
// and does not include any signed attributes like timestamp and so on.
|
|
//
|
|
// This function is needed to sign old Android APKs, something you probably
|
|
// shouldn't do unless you're maintaining backward compatibility for old
|
|
// applications.
|
|
func (sd *SignedData) SignWithoutAttr(ee *x509.Certificate, pkey crypto.PrivateKey, config SignerInfoConfig) error {
|
|
var signature []byte
|
|
sd.sd.DigestAlgorithmIdentifiers = append(sd.sd.DigestAlgorithmIdentifiers, pkix.AlgorithmIdentifier{Algorithm: sd.digestOid})
|
|
hash, err := getHashForOID(sd.digestOid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
h := hash.New()
|
|
h.Write(sd.data)
|
|
sd.messageDigest = h.Sum(nil)
|
|
switch pkey := pkey.(type) {
|
|
case *dsa.PrivateKey:
|
|
// dsa doesn't implement crypto.Signer so we make a special case
|
|
// https://github.com/golang/go/issues/27889
|
|
r, s, err := dsa.Sign(rand.Reader, pkey, sd.messageDigest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
signature, err = asn1.Marshal(dsaSignature{r, s})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
key, ok := pkey.(crypto.Signer)
|
|
if !ok {
|
|
return errors.New("pkcs7: private key does not implement crypto.Signer")
|
|
}
|
|
signature, err = key.Sign(rand.Reader, sd.messageDigest, hash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
var ias issuerAndSerial
|
|
ias.SerialNumber = ee.SerialNumber
|
|
// no parent, the issue is the end-entity cert itself
|
|
ias.IssuerName = asn1.RawValue{FullBytes: ee.RawIssuer}
|
|
if sd.encryptionOid == nil {
|
|
// if the encryption algorithm wasn't set by SetEncryptionAlgorithm,
|
|
// infer it from the digest algorithm
|
|
sd.encryptionOid, err = getOIDForEncryptionAlgorithm(pkey, sd.digestOid)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
signer := signerInfo{
|
|
DigestAlgorithm: pkix.AlgorithmIdentifier{Algorithm: sd.digestOid},
|
|
DigestEncryptionAlgorithm: pkix.AlgorithmIdentifier{Algorithm: sd.encryptionOid},
|
|
IssuerAndSerialNumber: ias,
|
|
EncryptedDigest: signature,
|
|
Version: 1,
|
|
}
|
|
// create signature of signed attributes
|
|
sd.certs = append(sd.certs, ee)
|
|
sd.sd.SignerInfos = append(sd.sd.SignerInfos, signer)
|
|
return nil
|
|
}
|
|
|
|
func (si *signerInfo) SetUnauthenticatedAttributes(extraUnsignedAttrs []Attribute) error {
|
|
unsignedAttrs := &attributes{}
|
|
for _, attr := range extraUnsignedAttrs {
|
|
unsignedAttrs.Add(attr.Type, attr.Value)
|
|
}
|
|
finalUnsignedAttrs, err := unsignedAttrs.ForMarshalling()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
si.UnauthenticatedAttributes = finalUnsignedAttrs
|
|
|
|
return nil
|
|
}
|
|
|
|
// AddCertificate adds the certificate to the payload. Useful for parent certificates
|
|
func (sd *SignedData) AddCertificate(cert *x509.Certificate) {
|
|
sd.certs = append(sd.certs, cert)
|
|
}
|
|
|
|
// Detach removes content from the signed data struct to make it a detached signature.
|
|
// This must be called right before Finish()
|
|
func (sd *SignedData) Detach() {
|
|
sd.sd.ContentInfo = contentInfo{ContentType: OIDData}
|
|
}
|
|
|
|
// GetSignedData returns the private Signed Data
|
|
func (sd *SignedData) GetSignedData() *signedData {
|
|
return &sd.sd
|
|
}
|
|
|
|
// Finish marshals the content and its signers
|
|
func (sd *SignedData) Finish() ([]byte, error) {
|
|
sd.sd.Certificates = marshalCertificates(sd.certs)
|
|
inner, err := asn1.Marshal(sd.sd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
outer := contentInfo{
|
|
ContentType: OIDSignedData,
|
|
Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: inner, IsCompound: true},
|
|
}
|
|
return asn1.Marshal(outer)
|
|
}
|
|
|
|
// RemoveAuthenticatedAttributes removes authenticated attributes from signedData
|
|
// similar to OpenSSL's PKCS7_NOATTR or -noattr flags
|
|
func (sd *SignedData) RemoveAuthenticatedAttributes() {
|
|
for i := range sd.sd.SignerInfos {
|
|
sd.sd.SignerInfos[i].AuthenticatedAttributes = nil
|
|
}
|
|
}
|
|
|
|
// RemoveUnauthenticatedAttributes removes unauthenticated attributes from signedData
|
|
func (sd *SignedData) RemoveUnauthenticatedAttributes() {
|
|
for i := range sd.sd.SignerInfos {
|
|
sd.sd.SignerInfos[i].UnauthenticatedAttributes = nil
|
|
}
|
|
}
|
|
|
|
// verifyPartialChain checks that a given cert is issued by the first parent in the list,
|
|
// then continue down the path. It doesn't require the last parent to be a root CA,
|
|
// or to be trusted in any truststore. It simply verifies that the chain provided, albeit
|
|
// partial, makes sense.
|
|
func verifyPartialChain(cert *x509.Certificate, parents []*x509.Certificate) error {
|
|
if len(parents) == 0 {
|
|
return fmt.Errorf("pkcs7: zero parents provided to verify the signature of certificate %q", cert.Subject.CommonName)
|
|
}
|
|
err := cert.CheckSignatureFrom(parents[0])
|
|
if err != nil {
|
|
return fmt.Errorf("pkcs7: certificate signature from parent is invalid: %v", err)
|
|
}
|
|
if len(parents) == 1 {
|
|
// there is no more parent to check, return
|
|
return nil
|
|
}
|
|
return verifyPartialChain(parents[0], parents[1:])
|
|
}
|
|
|
|
func cert2issuerAndSerial(cert *x509.Certificate) (issuerAndSerial, error) {
|
|
var ias issuerAndSerial
|
|
// The issuer RDNSequence has to match exactly the sequence in the certificate
|
|
// We cannot use cert.Issuer.ToRDNSequence() here since it mangles the sequence
|
|
ias.IssuerName = asn1.RawValue{FullBytes: cert.RawIssuer}
|
|
ias.SerialNumber = cert.SerialNumber
|
|
|
|
return ias, nil
|
|
}
|
|
|
|
// signs the DER encoded form of the attributes with the private key
|
|
func signAttributes(attrs []attribute, pkey crypto.PrivateKey, digestAlg crypto.Hash) ([]byte, error) {
|
|
attrBytes, err := marshalAttributes(attrs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
h := digestAlg.New()
|
|
h.Write(attrBytes)
|
|
hash := h.Sum(nil)
|
|
|
|
// dsa doesn't implement crypto.Signer so we make a special case
|
|
// https://github.com/golang/go/issues/27889
|
|
switch pkey := pkey.(type) {
|
|
case *dsa.PrivateKey:
|
|
r, s, err := dsa.Sign(rand.Reader, pkey, hash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return asn1.Marshal(dsaSignature{r, s})
|
|
}
|
|
|
|
key, ok := pkey.(crypto.Signer)
|
|
if !ok {
|
|
return nil, errors.New("pkcs7: private key does not implement crypto.Signer")
|
|
}
|
|
return key.Sign(rand.Reader, hash, digestAlg)
|
|
}
|
|
|
|
type dsaSignature struct {
|
|
R, S *big.Int
|
|
}
|
|
|
|
// concats and wraps the certificates in the RawValue structure
|
|
func marshalCertificates(certs []*x509.Certificate) rawCertificates {
|
|
var buf bytes.Buffer
|
|
for _, cert := range certs {
|
|
buf.Write(cert.Raw)
|
|
}
|
|
rawCerts, _ := marshalCertificateBytes(buf.Bytes())
|
|
return rawCerts
|
|
}
|
|
|
|
// Even though, the tag & length are stripped out during marshalling the
|
|
// RawContent, we have to encode it into the RawContent. If its missing,
|
|
// then `asn1.Marshal()` will strip out the certificate wrapper instead.
|
|
func marshalCertificateBytes(certs []byte) (rawCertificates, error) {
|
|
val := asn1.RawValue{Bytes: certs, Class: 2, Tag: 0, IsCompound: true}
|
|
b, err := asn1.Marshal(val)
|
|
if err != nil {
|
|
return rawCertificates{}, err
|
|
}
|
|
return rawCertificates{Raw: b}, nil
|
|
}
|
|
|
|
// DegenerateCertificate creates a signed data structure containing only the
|
|
// provided certificate or certificate chain.
|
|
func DegenerateCertificate(cert []byte) ([]byte, error) {
|
|
rawCert, err := marshalCertificateBytes(cert)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
emptyContent := contentInfo{ContentType: OIDData}
|
|
sd := signedData{
|
|
Version: 1,
|
|
ContentInfo: emptyContent,
|
|
Certificates: rawCert,
|
|
CRLs: []pkix.CertificateList{},
|
|
}
|
|
content, err := asn1.Marshal(sd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
signedContent := contentInfo{
|
|
ContentType: OIDSignedData,
|
|
Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: content, IsCompound: true},
|
|
}
|
|
return asn1.Marshal(signedContent)
|
|
}
|