1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
|
package x509bundle
import (
"crypto/x509"
"fmt"
"io"
"os"
"sync"
"github.com/spiffe/go-spiffe/v2/internal/pemutil"
"github.com/spiffe/go-spiffe/v2/internal/x509util"
"github.com/spiffe/go-spiffe/v2/spiffeid"
)
// Bundle is a collection of trusted X.509 authorities for a trust domain.
type Bundle struct {
trustDomain spiffeid.TrustDomain
mtx sync.RWMutex
x509Authorities []*x509.Certificate
}
// New creates a new bundle.
func New(trustDomain spiffeid.TrustDomain) *Bundle {
return &Bundle{
trustDomain: trustDomain,
}
}
// FromX509Authorities creates a bundle from X.509 certificates.
func FromX509Authorities(trustDomain spiffeid.TrustDomain, authorities []*x509.Certificate) *Bundle {
return &Bundle{
trustDomain: trustDomain,
x509Authorities: x509util.CopyX509Authorities(authorities),
}
}
// Load loads a bundle from a file on disk. The file must contain PEM-encoded
// certificate blocks.
func Load(trustDomain spiffeid.TrustDomain, path string) (*Bundle, error) {
fileBytes, err := os.ReadFile(path)
if err != nil {
return nil, wrapX509bundleErr(fmt.Errorf("unable to load X.509 bundle file: %w", err))
}
return Parse(trustDomain, fileBytes)
}
// Read decodes a bundle from a reader. The contents must be PEM-encoded
// certificate blocks.
func Read(trustDomain spiffeid.TrustDomain, r io.Reader) (*Bundle, error) {
b, err := io.ReadAll(r)
if err != nil {
return nil, wrapX509bundleErr(fmt.Errorf("unable to read X.509 bundle: %v", err))
}
return Parse(trustDomain, b)
}
// Parse parses a bundle from bytes. The data must be PEM-encoded certificate
// blocks.
func Parse(trustDomain spiffeid.TrustDomain, b []byte) (*Bundle, error) {
bundle := New(trustDomain)
if len(b) == 0 {
return bundle, nil
}
certs, err := pemutil.ParseCertificates(b)
if err != nil {
return nil, wrapX509bundleErr(fmt.Errorf("cannot parse certificate: %v", err))
}
for _, cert := range certs {
bundle.AddX509Authority(cert)
}
return bundle, nil
}
// ParseRaw parses a bundle from bytes. The certificate must be ASN.1 DER (concatenated
// with no intermediate padding if there are more than one certificate)
func ParseRaw(trustDomain spiffeid.TrustDomain, b []byte) (*Bundle, error) {
bundle := New(trustDomain)
if len(b) == 0 {
return bundle, nil
}
certs, err := x509.ParseCertificates(b)
if err != nil {
return nil, wrapX509bundleErr(fmt.Errorf("cannot parse certificate: %v", err))
}
for _, cert := range certs {
bundle.AddX509Authority(cert)
}
return bundle, nil
}
// TrustDomain returns the trust domain that the bundle belongs to.
func (b *Bundle) TrustDomain() spiffeid.TrustDomain {
return b.trustDomain
}
// X509Authorities returns the X.509 x509Authorities in the bundle.
func (b *Bundle) X509Authorities() []*x509.Certificate {
b.mtx.RLock()
defer b.mtx.RUnlock()
return x509util.CopyX509Authorities(b.x509Authorities)
}
// AddX509Authority adds an X.509 authority to the bundle. If the authority already
// exists in the bundle, the contents of the bundle will remain unchanged.
func (b *Bundle) AddX509Authority(x509Authority *x509.Certificate) {
b.mtx.Lock()
defer b.mtx.Unlock()
for _, r := range b.x509Authorities {
if r.Equal(x509Authority) {
return
}
}
b.x509Authorities = append(b.x509Authorities, x509Authority)
}
// RemoveX509Authority removes an X.509 authority from the bundle.
func (b *Bundle) RemoveX509Authority(x509Authority *x509.Certificate) {
b.mtx.Lock()
defer b.mtx.Unlock()
for i, r := range b.x509Authorities {
if r.Equal(x509Authority) {
// remove element from slice
b.x509Authorities = append(b.x509Authorities[:i], b.x509Authorities[i+1:]...)
return
}
}
}
// HasX509Authority checks if the given X.509 authority exists in the bundle.
func (b *Bundle) HasX509Authority(x509Authority *x509.Certificate) bool {
b.mtx.RLock()
defer b.mtx.RUnlock()
for _, r := range b.x509Authorities {
if r.Equal(x509Authority) {
return true
}
}
return false
}
// SetX509Authorities sets the X.509 authorities in the bundle.
func (b *Bundle) SetX509Authorities(x509Authorities []*x509.Certificate) {
b.mtx.Lock()
defer b.mtx.Unlock()
b.x509Authorities = x509util.CopyX509Authorities(x509Authorities)
}
// Empty returns true if the bundle has no X.509 x509Authorities.
func (b *Bundle) Empty() bool {
b.mtx.RLock()
defer b.mtx.RUnlock()
return len(b.x509Authorities) == 0
}
// Marshal marshals the X.509 bundle into PEM-encoded certificate blocks.
func (b *Bundle) Marshal() ([]byte, error) {
b.mtx.RLock()
defer b.mtx.RUnlock()
return pemutil.EncodeCertificates(b.x509Authorities), nil
}
// Equal compares the bundle for equality against the given bundle.
func (b *Bundle) Equal(other *Bundle) bool {
if b == nil || other == nil {
return b == other
}
return b.trustDomain == other.trustDomain &&
x509util.CertsEqual(b.x509Authorities, other.x509Authorities)
}
// Clone clones the bundle.
func (b *Bundle) Clone() *Bundle {
b.mtx.RLock()
defer b.mtx.RUnlock()
return FromX509Authorities(b.trustDomain, b.x509Authorities)
}
// GetX509BundleForTrustDomain returns the X.509 bundle for the given trust
// domain. It implements the Source interface. An error will be
// returned if the trust domain does not match that of the bundle.
func (b *Bundle) GetX509BundleForTrustDomain(trustDomain spiffeid.TrustDomain) (*Bundle, error) {
if b.trustDomain != trustDomain {
return nil, wrapX509bundleErr(fmt.Errorf("no X.509 bundle found for trust domain: %q", trustDomain))
}
return b, nil
}
func wrapX509bundleErr(err error) error {
return fmt.Errorf("x509bundle: %w", err)
}
|