Skip to content

Commit df5db5c

Browse files
Extend signers to set CRL Distribution Points
- Introduced support for Certificate Revocation List (CRL) distribution points in the certificate signing process. - Enhanced identity certificate generation to include CRL distribution points. - Added a method for validating and processing CRL distribution points in the configuration. - Updated the BasicCertificateSigner to include CRL distribution points during certificate signing. - Implemented a validation function for CRL distribution points. - Added new functions for parsing and checking Certificate Signing Requests (CSRs).
1 parent 488551e commit df5db5c

24 files changed

Lines changed: 924 additions & 120 deletions

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ unit-test: certificates
120120
go test -race -parallel 1 -v ./bridge/... -coverpkg=./... -covermode=atomic -coverprofile=$(TMP_PATH)/bridge.unit.coverage.txt
121121
go test -race -v ./schema/... -covermode=atomic -coverprofile=$(TMP_PATH)/schema.unit.coverage.txt
122122
ROOT_CA_CRT="$(ROOT_CA_CRT)" ROOT_CA_KEY="$(ROOT_CA_KEY)" \
123+
INTERMEDIATE_CA_CRT="$(INTERMEDIATE_CA_CRT)" INTERMEDIATE_CA_KEY=$(INTERMEDIATE_CA_KEY) \
123124
go test -race -v ./pkg/... -covermode=atomic -coverprofile=$(TMP_PATH)/pkg.unit.coverage.txt
124125

125126
test: env build-testcontainer

client/deviceOwnershipSDK.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,14 @@ type Signer = interface {
3939
}
4040

4141
type DeviceOwnershipSDKConfig struct {
42-
ID string
43-
Cert string
44-
CertKey string
45-
ValidFrom string // RFC3339, or now-1m, empty means now-1m
46-
CertExpiry *string
47-
48-
CreateSignerFunc func(caCert []*x509.Certificate, caKey crypto.PrivateKey, validNotBefore time.Time, validNotAfter time.Time) core.CertificateSigner
42+
ID string
43+
Cert string
44+
CertKey string
45+
ValidFrom string // RFC3339, or now-1m, empty means now-1m
46+
CertExpiry *string
47+
CRLDistributionPoints []string
48+
49+
CreateSignerFunc func(caCert []*x509.Certificate, caKey crypto.PrivateKey, validNotBefore, validNotAfter time.Time, crlDistributionPoints []string) (core.CertificateSigner, error)
4950
}
5051

5152
type deviceOwnershipSDK struct {
@@ -78,11 +79,11 @@ func newDeviceOwnershipSDKFromConfig(app ApplicationCallback, dialTLS core.DialT
7879
return nil, fmt.Errorf("invalid ID for device ownership SDK: %w", err)
7980
}
8081

81-
return newDeviceOwnershipSDK(app, uid.String(), dialTLS, dialDLTS, &signerCert, cfg.ValidFrom, certExpiry, cfg.CreateSignerFunc)
82+
return newDeviceOwnershipSDK(app, uid.String(), dialTLS, dialDLTS, &signerCert, cfg.ValidFrom, certExpiry, cfg.CRLDistributionPoints, cfg.CreateSignerFunc)
8283
}
8384

8485
func newDeviceOwnershipSDK(app ApplicationCallback, sdkDeviceID string, dialTLS core.DialTLS,
85-
dialDTLS core.DialDTLS, signerCert *tls.Certificate, validFrom string, certExpiry time.Duration, createSigner func(caCert []*x509.Certificate, caKey crypto.PrivateKey, validNotBefore time.Time, validNotAfter time.Time) core.CertificateSigner,
86+
dialDTLS core.DialDTLS, signerCert *tls.Certificate, validFrom string, certExpiry time.Duration, crlDistributionPoints []string, createSigner func(caCert []*x509.Certificate, caKey crypto.PrivateKey, validNotBefore, validNotAfter time.Time, crlDistributionPoints []string) (core.CertificateSigner, error),
8687
) (*deviceOwnershipSDK, error) {
8788
if validFrom == "" {
8889
validFrom = "now-1m"
@@ -107,7 +108,7 @@ func newDeviceOwnershipSDK(app ApplicationCallback, sdkDeviceID string, dialTLS
107108
return nil, fmt.Errorf("invalid validFrom(%v): %w", validFrom, err)
108109
}
109110
notAfter := notBefore.Add(certExpiry)
110-
return createSigner(signerCAs, signerCert.PrivateKey, notBefore, notAfter), nil
111+
return createSigner(signerCAs, signerCert.PrivateKey, notBefore, notAfter, crlDistributionPoints)
111112
},
112113
app: app,
113114
dialTLS: dialTLS,

cmd/bridge-device/Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ WORKDIR $GOPATH/src/github.com/plgd-dev/device
1111
RUN CGO_ENABLED=0 go build -o /go/bin/bridge-device ./cmd/bridge-device
1212

1313
FROM alpine:3.20 AS security-provider
14-
RUN apk add -U --no-cache ca-certificates
15-
RUN addgroup -S nonroot \
14+
RUN apk add -U --no-cache ca-certificates \
15+
&& addgroup -S nonroot \
1616
&& adduser -S nonroot -G nonroot
1717

1818
FROM scratch AS service

cmd/ocfclient/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -523,8 +523,8 @@ func NewSecureClient() (*local.Client, error) {
523523
ID: CertIdentity,
524524
Cert: string(IdentityIntermediateCA),
525525
CertKey: string(IdentityIntermediateCAKey),
526-
CreateSignerFunc: func(caCert []*x509.Certificate, caKey crypto.PrivateKey, validNotBefore time.Time, validNotAfter time.Time) core.CertificateSigner {
527-
return signer.NewOCFIdentityCertificate(caCert, caKey, validNotBefore, validNotAfter)
526+
CreateSignerFunc: func(caCert []*x509.Certificate, caKey crypto.PrivateKey, validNotBefore, validNotAfter time.Time, crlDistributionPoints []string) (core.CertificateSigner, error) {
527+
return signer.NewOCFIdentityCertificate(caCert, caKey, validNotBefore, validNotAfter, crlDistributionPoints)
528528
},
529529
},
530530
}

pkg/security/generateCertificate/config.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ import (
99
"encoding/asn1"
1010
"fmt"
1111
"net"
12+
"slices"
1213
"strconv"
1314
"strings"
1415
"time"
16+
17+
pkgX509 "github.com/plgd-dev/device/v2/pkg/security/x509"
1518
)
1619

1720
type (
@@ -56,9 +59,10 @@ type Configuration struct {
5659
//nolint:staticcheck
5760
KeyUsages []string `yaml:"keyUsages" long:"ku" default:"digitalSignature" default:"keyAgreement" description:"to set more values repeat option with parameter"`
5861
//nolint:staticcheck
59-
ExtensionKeyUsages []string `yaml:"extensionKeyUsages" long:"eku" default:"client" default:"server" description:"to set more values repeat option with parameter"`
60-
EllipticCurve EllipticCurve `yaml:"ellipticCurve" long:"ellipticCurve" default:"P256" description:"supported values:P256, P384, P521"`
61-
SignatureAlgorithm SignatureAlgorithm `yaml:"signatureAlgorithm" long:"signatureAlgorithm" default:"ECDSA-SHA256" description:"supported values:ECDSA-SHA256, ECDSA-SHA384, ECDSA-SHA512"`
62+
ExtensionKeyUsages []string `yaml:"extensionKeyUsages" long:"eku" default:"client" default:"server" description:"to set more values repeat option with parameter"`
63+
EllipticCurve EllipticCurve `yaml:"ellipticCurve" long:"ellipticCurve" default:"P256" description:"supported values:P256, P384, P521"`
64+
SignatureAlgorithm SignatureAlgorithm `yaml:"signatureAlgorithm" long:"signatureAlgorithm" default:"ECDSA-SHA256" description:"supported values:ECDSA-SHA256, ECDSA-SHA384, ECDSA-SHA512"`
65+
CRLDistributionPoints []string `yaml:"crlDistributionPoints" long:"crl" description:"to set more values repeat option with parameter"`
6266
}
6367

6468
func (cfg Configuration) ToPkixName() pkix.Name {
@@ -303,3 +307,12 @@ func (cfg Configuration) ToIPAddresses() ([]net.IP, error) {
303307
}
304308
return ips, nil
305309
}
310+
311+
func (cfg Configuration) ToCRLDistributionPoints() ([]string, error) {
312+
if err := pkgX509.ValidateCRLDistributionPoints(cfg.CRLDistributionPoints); err != nil {
313+
return nil, err
314+
}
315+
cdp := slices.Clone(cfg.CRLDistributionPoints)
316+
slices.Sort(cdp)
317+
return slices.Compact(cdp), nil
318+
}

pkg/security/generateCertificate/config_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,60 @@ func TestToIPAddresses(t *testing.T) {
112112
expected := []net.IP{net.ParseIP("192.168.0.1"), net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")}
113113
require.Equal(t, expected, ips)
114114
}
115+
116+
func TestToCRLDistributionPoints(t *testing.T) {
117+
tests := []struct {
118+
name string
119+
cfg generateCertificate.Configuration
120+
want []string
121+
wantErr bool
122+
}{
123+
{
124+
name: "Valid CRL URLs",
125+
cfg: generateCertificate.Configuration{
126+
CRLDistributionPoints: []string{
127+
"http://example.com/crl1",
128+
"http://example.com/crl2",
129+
},
130+
},
131+
want: []string{"http://example.com/crl1", "http://example.com/crl2"},
132+
},
133+
{
134+
name: "Duplicate CRL URLs",
135+
cfg: generateCertificate.Configuration{
136+
CRLDistributionPoints: []string{
137+
"http://example.com/crl1",
138+
"http://example.com/crl1", // duplicate
139+
},
140+
},
141+
want: []string{"http://example.com/crl1"},
142+
},
143+
{
144+
name: "Invalid CRL URL",
145+
cfg: generateCertificate.Configuration{
146+
CRLDistributionPoints: []string{
147+
"invalid-url",
148+
},
149+
},
150+
wantErr: true,
151+
},
152+
{
153+
name: "Empty CRL list",
154+
cfg: generateCertificate.Configuration{
155+
CRLDistributionPoints: []string{},
156+
},
157+
want: []string{},
158+
},
159+
}
160+
161+
for _, tt := range tests {
162+
t.Run(tt.name, func(t *testing.T) {
163+
crls, err := tt.cfg.ToCRLDistributionPoints()
164+
if tt.wantErr {
165+
require.Error(t, err)
166+
return
167+
}
168+
require.ElementsMatch(t, tt.want, crls)
169+
})
170+
}
171+
}

pkg/security/generateCertificate/generateCertificate.go

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@ import (
99
"encoding/asn1"
1010
"encoding/pem"
1111

12-
ocfSigner "github.com/plgd-dev/kit/v2/security/signer"
12+
ocfSigner "github.com/plgd-dev/device/v2/pkg/security/signer"
13+
)
14+
15+
var (
16+
ASN1KeyUsage = asn1.ObjectIdentifier{2, 5, 29, 15}
17+
ASN1BasicConstraints = asn1.ObjectIdentifier{2, 5, 29, 19}
18+
ASN1ExtKeyUsage = asn1.ObjectIdentifier{2, 5, 29, 37}
1319
)
1420

1521
// GenerateCSR creates CSR according to configuration.
@@ -28,12 +34,12 @@ func GenerateCSR(cfg Configuration, privateKey *ecdsa.PrivateKey) ([]byte, error
2834

2935
extraExtensions := make([]pkix.Extension, 0, 3)
3036
if !cfg.BasicConstraints.Ignore {
31-
bcVal, errM := asn1.Marshal(basicConstraints{false})
37+
bcVal, errM := asn1.Marshal(BasicConstraints{false})
3238
if errM != nil {
3339
return nil, errM
3440
}
3541
extraExtensions = append(extraExtensions, pkix.Extension{
36-
Id: asn1.ObjectIdentifier{2, 5, 29, 19}, // basic constraints
42+
Id: ASN1BasicConstraints,
3743
Value: bcVal,
3844
Critical: false,
3945
})
@@ -49,7 +55,7 @@ func GenerateCSR(cfg Configuration, privateKey *ecdsa.PrivateKey) ([]byte, error
4955
return nil, errM
5056
}
5157
extraExtensions = append(extraExtensions, pkix.Extension{
52-
Id: asn1.ObjectIdentifier{2, 5, 29, 15}, // key usage
58+
Id: ASN1KeyUsage,
5359
Value: val,
5460
Critical: false,
5561
})
@@ -65,7 +71,7 @@ func GenerateCSR(cfg Configuration, privateKey *ecdsa.PrivateKey) ([]byte, error
6571
return nil, errM
6672
}
6773
extraExtensions = append(extraExtensions, pkix.Extension{
68-
Id: asn1.ObjectIdentifier{2, 5, 29, 37}, // EKU
74+
Id: ASN1ExtKeyUsage,
6975
Value: val,
7076
Critical: false,
7177
})
@@ -100,8 +106,14 @@ func GenerateCert(cfg Configuration, privateKey *ecdsa.PrivateKey, signerCA []*x
100106
if err != nil {
101107
return nil, err
102108
}
103-
104109
notAfter := notBefore.Add(cfg.ValidFor)
105-
s := ocfSigner.NewBasicCertificateSigner(signerCA, signerCAKey, notBefore, notAfter)
110+
crlDistributionPoints, err := cfg.ToCRLDistributionPoints()
111+
if err != nil {
112+
return nil, err
113+
}
114+
s, err := ocfSigner.NewBasicCertificateSigner(signerCA, signerCAKey, notBefore, notAfter, crlDistributionPoints)
115+
if err != nil {
116+
return nil, err
117+
}
106118
return s.Sign(context.Background(), csr)
107119
}

0 commit comments

Comments
 (0)