Skip to content

Commit a3f906d

Browse files
authored
Adds crypto/spiffe (#92)
* Adds crypto/spiffe Adds spiffe package to crypto. This is a refactored version of the existing `pkg/security` package. This new package is more modulated and fuller test coverage. This package has been moved so that it can be both imported by dapr & components-contrib, as well as making the package more suitable for further development to support X.509 Component auth. dapr/proposals#51 Also moves in `test/utils` from dapr to `crypto/test` for shared usage. Signed-off-by: joshvanl <[email protected]> * Adds crypto/spiffe/context Signed-off-by: joshvanl <[email protected]> --------- Signed-off-by: joshvanl <[email protected]>
1 parent 0c7cfce commit a3f906d

File tree

14 files changed

+2077
-51
lines changed

14 files changed

+2077
-51
lines changed

crypto/pem/pem.go

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
/*
2+
Copyright 2023 The Dapr Authors
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package pem
15+
16+
import (
17+
"bytes"
18+
"crypto"
19+
"crypto/ecdsa"
20+
"crypto/ed25519"
21+
"crypto/rsa"
22+
"crypto/x509"
23+
"encoding/pem"
24+
"errors"
25+
"fmt"
26+
)
27+
28+
// DecodePEMCertificatesChain takes a PEM-encoded x509 certificates byte array
29+
// and returns all certificates in a slice of x509.Certificate objects.
30+
// Expects certificates to be a chain with leaf certificate to be first in the
31+
// byte array.
32+
func DecodePEMCertificatesChain(crtb []byte) ([]*x509.Certificate, error) {
33+
certs, err := DecodePEMCertificates(crtb)
34+
if err != nil {
35+
return nil, err
36+
}
37+
38+
for i := 0; i < len(certs)-1; i++ {
39+
if certs[i].CheckSignatureFrom(certs[i+1]) != nil {
40+
return nil, errors.New("certificate chain is not valid")
41+
}
42+
}
43+
44+
return certs, nil
45+
}
46+
47+
// DecodePEMCertificatesChain takes a PEM-encoded x509 certificates byte array
48+
// and returns all certificates in a slice of x509.Certificate objects.
49+
func DecodePEMCertificates(crtb []byte) ([]*x509.Certificate, error) {
50+
certs := []*x509.Certificate{}
51+
for len(crtb) > 0 {
52+
var err error
53+
var cert *x509.Certificate
54+
55+
cert, crtb, err = decodeCertificatePEM(crtb)
56+
if err != nil {
57+
return nil, err
58+
}
59+
if cert != nil {
60+
// it's a cert, add to pool
61+
certs = append(certs, cert)
62+
}
63+
}
64+
65+
if len(certs) == 0 {
66+
return nil, errors.New("no certificates found")
67+
}
68+
69+
return certs, nil
70+
}
71+
72+
func decodeCertificatePEM(crtb []byte) (*x509.Certificate, []byte, error) {
73+
block, crtb := pem.Decode(crtb)
74+
if block == nil {
75+
return nil, nil, nil
76+
}
77+
if block.Type != "CERTIFICATE" {
78+
return nil, nil, nil
79+
}
80+
c, err := x509.ParseCertificate(block.Bytes)
81+
return c, crtb, err
82+
}
83+
84+
// DecodePEMPrivateKey takes a key PEM byte array and returns an object that
85+
// represents either an RSA or EC private key.
86+
func DecodePEMPrivateKey(key []byte) (crypto.Signer, error) {
87+
block, _ := pem.Decode(key)
88+
if block == nil {
89+
return nil, errors.New("key is not PEM encoded")
90+
}
91+
92+
switch block.Type {
93+
case "EC PRIVATE KEY":
94+
return x509.ParseECPrivateKey(block.Bytes)
95+
case "RSA PRIVATE KEY":
96+
return x509.ParsePKCS1PrivateKey(block.Bytes)
97+
case "PRIVATE KEY":
98+
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
99+
if err != nil {
100+
return nil, err
101+
}
102+
return key.(crypto.Signer), nil
103+
default:
104+
return nil, fmt.Errorf("unsupported block type %s", block.Type)
105+
}
106+
}
107+
108+
// EncodePrivateKey will encode a private key into PEM format.
109+
func EncodePrivateKey(key any) ([]byte, error) {
110+
var (
111+
keyBytes []byte
112+
err error
113+
blockType string
114+
)
115+
116+
switch key := key.(type) {
117+
case *ecdsa.PrivateKey, *ed25519.PrivateKey:
118+
keyBytes, err = x509.MarshalPKCS8PrivateKey(key)
119+
if err != nil {
120+
return nil, err
121+
}
122+
blockType = "PRIVATE KEY"
123+
default:
124+
return nil, fmt.Errorf("unsupported key type %T", key)
125+
}
126+
127+
return pem.EncodeToMemory(&pem.Block{
128+
Type: blockType, Bytes: keyBytes,
129+
}), nil
130+
}
131+
132+
// EncodeX509 will encode a single *x509.Certificate into PEM format.
133+
func EncodeX509(cert *x509.Certificate) ([]byte, error) {
134+
caPem := bytes.NewBuffer([]byte{})
135+
err := pem.Encode(caPem, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
136+
if err != nil {
137+
return nil, err
138+
}
139+
140+
return caPem.Bytes(), nil
141+
}
142+
143+
// EncodeX509Chain will encode a list of *x509.Certificates into a PEM format chain.
144+
// Self-signed certificates are not included as per
145+
// https://datatracker.ietf.org/doc/html/rfc5246#section-7.4.2
146+
// Certificates are output in the order they're given; if the input is not ordered
147+
// as specified in RFC5246 section 7.4.2, the resulting chain might not be valid
148+
// for use in TLS.
149+
func EncodeX509Chain(certs []*x509.Certificate) ([]byte, error) {
150+
if len(certs) == 0 {
151+
return nil, errors.New("no certificates in chain")
152+
}
153+
154+
certPEM := bytes.NewBuffer([]byte{})
155+
for _, cert := range certs {
156+
if cert == nil {
157+
continue
158+
}
159+
160+
if cert.CheckSignatureFrom(cert) == nil {
161+
// Don't include self-signed certificate
162+
continue
163+
}
164+
165+
err := pem.Encode(certPEM, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
166+
if err != nil {
167+
return nil, err
168+
}
169+
}
170+
171+
return certPEM.Bytes(), nil
172+
}
173+
174+
// PublicKeysEqual compares two given public keys for equality.
175+
// The definition of "equality" depends on the type of the public keys.
176+
// Returns true if the keys are the same, false if they differ or an error if
177+
// the key type of `a` cannot be determined.
178+
func PublicKeysEqual(a, b crypto.PublicKey) (bool, error) {
179+
switch pub := a.(type) {
180+
case *rsa.PublicKey:
181+
return pub.Equal(b), nil
182+
case *ecdsa.PublicKey:
183+
return pub.Equal(b), nil
184+
case ed25519.PublicKey:
185+
return pub.Equal(b), nil
186+
default:
187+
return false, fmt.Errorf("unrecognised public key type: %T", a)
188+
}
189+
}

crypto/spiffe/context/context.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
Copyright 2024 The Dapr Authors
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package context
15+
16+
import (
17+
"context"
18+
19+
"github.com/spiffe/go-spiffe/v2/svid/x509svid"
20+
21+
"github.com/dapr/kit/crypto/spiffe"
22+
)
23+
24+
type ctxkey int
25+
26+
const svidKey ctxkey = iota
27+
28+
func With(ctx context.Context, spiffe *spiffe.SPIFFE) context.Context {
29+
return context.WithValue(ctx, svidKey, spiffe.SVIDSource())
30+
}
31+
32+
func From(ctx context.Context) (x509svid.Source, bool) {
33+
svid, ok := ctx.Value(svidKey).(x509svid.Source)
34+
return svid, ok
35+
}

0 commit comments

Comments
 (0)