Skip to content

Commit 1a58c49

Browse files
authored
test: add unit test to downloader.go (#7)
1 parent d8a7746 commit 1a58c49

3 files changed

Lines changed: 224 additions & 7 deletions

File tree

.github/workflows/go-test.yaml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,6 @@ jobs:
2222
deps:
2323
- locked
2424
- latest
25-
# Exclude (oldstable, latest) and (go.mod, latest)
26-
# root cause: latest version of boulder requires Go 1.25.0
27-
# exclude:
28-
# - go: { go-version: oldstable }
29-
# deps: latest
30-
# - go: { go-version-file: go.mod }
31-
# deps: latest
3225
steps:
3326
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
3427
with:
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package validate
2+
3+
import (
4+
"context"
5+
"net/url"
6+
"testing"
7+
8+
tpmtest "github.com/loicsikidi/tpm-trust/internal/validate/testutil"
9+
)
10+
11+
func Test_downloadCRLSigner(t *testing.T) {
12+
t.Parallel()
13+
14+
tests := []struct {
15+
name string
16+
enabled bool
17+
url string
18+
wantErr bool
19+
}{
20+
{
21+
name: "success",
22+
enabled: true,
23+
url: "http://pki/signer.cer",
24+
wantErr: false,
25+
},
26+
{
27+
name: "disabled downloader",
28+
enabled: false,
29+
url: "http://pki/signer.cer",
30+
wantErr: true,
31+
},
32+
}
33+
34+
for _, tt := range tests {
35+
tc := tt
36+
t.Run(tc.name, func(t *testing.T) {
37+
t.Parallel()
38+
39+
var client *tpmtest.MockClient
40+
if tc.enabled {
41+
client = tpmtest.NewDefaultDowloaderMockClient(t)
42+
}
43+
44+
d := &downloader{
45+
enabled: tc.enabled,
46+
maxDownloads: 10,
47+
client: client,
48+
}
49+
50+
certURL, err := url.Parse(tc.url)
51+
if err != nil {
52+
t.Fatalf("failed to parse URL: %v", err)
53+
}
54+
55+
got, err := d.downloadCRLSigner(context.Background(), certURL)
56+
if (err != nil) != tc.wantErr {
57+
t.Errorf("downloader.downloadCRLSigner() error = %v, wantErr %v", err, tc.wantErr)
58+
return
59+
}
60+
61+
if err == nil {
62+
if got == nil {
63+
t.Error("downloader.downloadCRLSigner() returned nil certificate")
64+
return
65+
}
66+
if len(got.Raw) == 0 {
67+
t.Error("downloader.downloadCRLSigner() returned empty certificate")
68+
return
69+
}
70+
}
71+
})
72+
}
73+
}
74+
75+
func Test_downloadEKCertifiate(t *testing.T) {
76+
t.Parallel()
77+
client := tpmtest.NewDefaultDowloaderMockClient(t)
78+
tests := []struct {
79+
name string
80+
ctx context.Context
81+
ekURL string
82+
wantSubject string
83+
wantErr bool
84+
}{
85+
{
86+
name: "intel",
87+
ctx: context.Background(),
88+
ekURL: "https://ekop.intel.com/ekcertservice/WVEG2rRwkQ7m3RpXlUphgo6Y2HLxl18h6ZZkkOAdnBE%3D",
89+
wantSubject: "", // no subject in EK certificate
90+
wantErr: false,
91+
},
92+
{
93+
name: "amd EK CA root",
94+
ctx: context.Background(),
95+
ekURL: "https://ftpm.amd.com/pki/aia/264D39A23CEB5D5B49D610044EEBD121", // assumes AMD EK certificate responses are all in the same format
96+
wantSubject: "CN=AMDTPM,OU=Engineering,O=Advanced Micro Devices,L=Sunnyvale,ST=CA,C=US", // AMDTPM EK CA root subject
97+
wantErr: false,
98+
},
99+
}
100+
for _, tt := range tests {
101+
tc := tt
102+
t.Run(tt.name, func(t *testing.T) {
103+
t.Parallel()
104+
d := &downloader{enabled: true, maxDownloads: 10, client: client}
105+
ekURL, err := url.Parse(tc.ekURL)
106+
if err != nil {
107+
t.Fatalf("failed to parse URL: %v", err)
108+
}
109+
110+
got, err := d.downloadEKCertificate(tc.ctx, ekURL)
111+
if (err != nil) != tc.wantErr {
112+
t.Errorf("downloader.downloadEKCertifiate() error = %v, wantErr %v", err, tc.wantErr)
113+
return
114+
}
115+
116+
if err == nil {
117+
if got == nil {
118+
t.Error("downloader.downloadEKCertifiate() returned nil certificate")
119+
return
120+
}
121+
if len(got.Raw) == 0 {
122+
t.Error("downloader.downloadEKCertifiate() returned empty certificate")
123+
return
124+
}
125+
if got.Subject.String() != tc.wantSubject {
126+
t.Errorf("downloader.downloadEKCertifiate() = %v, want %v", got.Subject.String(), tc.wantSubject)
127+
}
128+
}
129+
})
130+
}
131+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package tpm
2+
3+
import (
4+
"bytes"
5+
"encoding/base64"
6+
"errors"
7+
"io"
8+
"net/http"
9+
"testing"
10+
"time"
11+
)
12+
13+
const (
14+
DefaultDownloadTimeout = 10 * time.Millisecond
15+
// JSON response for https://ekop.intel.com/ekcertservice/WVEG2rRwkQ7m3RpXlUphgo6Y2HLxl18h6ZZkkOAdnBE%3D; also see https://github.com/tpm2-software/tpm2-tools/blob/master/test/integration/tests/getekcertificate.sh
16+
intelEKMockResponse = `{"pubhash":"WVEG2rRwkQ7m3RpXlUphgo6Y2HLxl18h6ZZkkOAdnBE%3D","certificate":"MIIEnDCCBEOgAwIBAgIEfT80-DAKBggqhkjOPQQDAjCBlTELMAkGA1UEBgwCVVMxCzAJBgNVBAgMAkNBMRQwEgYDVQQHDAtTYW50YSBDbGFyYTEaMBgGA1UECgwRSW50ZWwgQ29ycG9yYXRpb24xLzAtBgNVBAsMJlRQTSBFSyBpbnRlcm1lZGlhdGUgZm9yIFNQVEhfRVBJRF9QUk9EMRYwFAYDVQQDDA13d3cuaW50ZWwuY29tMB4XDTE1MDUyMjAwMDAwMFoXDTQ5MTIzMTIzNTk1OVowADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMMg4vJEqGAarPPgHSbGZSSZNVYt4doZfp5_B2xGlhPPtlPpjsLDhvwdEz8sjGzDOLcy8LIIvYOKh3o-W7w-HUCE6DXHyJBqHAW00tMP2-vB262VD6axZb1LaoZGAxRhZMDE9Z1IkBHvH5KN7qbpAGHz03XlZGJzFR72IiUgmL4aSrAdwKEiJ8YJ_azrEVr0CNRpOm9JkZd0aVsMErwYof9xIKczey-18ZUdi7fwlNW1VMEclSOzByn-ZHh9ChO55jBIjatN_YZjSlJw7HL8xaRNxnmo8yk43YGX4p2ug59bTKD13ifJUiwjxU4cLOV4WVJRGL1EcLGBgO73iuQme80CAwEAAaOCAkgwggJEMA8GA1UdEwEB_wQFMAMBAQAwDgYDVR0PAQH_BAQDAgAgMBAGA1UdJQQJMAcGBWeBBQgBMCQGA1UdCQEBAAQaMBgwFgYFZ4EFAhAxDTALDAMyLjACAQACAWcwUAYDVR0RAQH_BEYwRKRCMEAxFjAUBgVngQUCAQwLaWQ6NDk0RTU0NDMxDjAMBgVngQUCAgwDU1BUMRYwFAYFZ4EFAgMMC2lkOjAwMDIwMDAwMB8GA1UdIwQYMBaAFF5zyJqj6QKycrnwdB99hzDj7HJKMFgGA1UdHwRRME8wTaBLoEmGR2h0dHA6Ly91cGdyYWRlcy5pbnRlbC5jb20vY29udGVudC9DUkwvZWtjZXJ0L1NQVEhFUElEUFJPRF9FS19EZXZpY2UuY3JsMHAGCCsGAQUFBwEBBGQwYjBgBggrBgEFBQcwAoZUaHR0cDovL3VwZ3JhZGVzLmludGVsLmNvbS9jb250ZW50L0NSTC9la2NlcnQvU1BUSEVQSURQUk9EX0VLX1BsYXRmb3JtX1B1YmxpY19LZXkuY2VyMIGpBgNVHSAEgaEwgZ4wgZsGCiqGSIb4TQEFAgEwgYwwUgYIKwYBBQUHAgEWRmh0dHA6Ly91cGdyYWRlcy5pbnRlbC5jb20vY29udGVudC9DUkwvZWtjZXJ0L0VLY2VydFBvbGljeVN0YXRlbWVudC5wZGYwNgYIKwYBBQUHAgIwKgwoVENQQSBUcnVzdGVkIFBsYXRmb3JtIE1vZHVsZSBFbmRvcnNlbWVudDAKBggqhkjOPQQDAgNHADBEAiBrQr0ckEoWsrx0971bppP6N8PTb4U6z_hIqpS6o150xAIgNxZNXq7bCqU1b4hGdiSBauowiOVFcaaiTm1p99H_k1Q%3D"}`
17+
18+
// base64 encoded response for https://ftpm.amd.com/pki/aia/264D39A23CEB5D5B49D610044EEBD121
19+
amdEKRootMockResponseBase64 = `MIIEiDCCA3CgAwIBAgIQJk05ojzrXVtJ1hAETuvRITANBgkqhkiG9w0BAQsFADB2MRQwEgYDVQQLEwtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxEjAQBgNVBAcTCVN1bm55dmFsZTELMAkGA1UECBMCQ0ExHzAdBgNVBAoTFkFkdmFuY2VkIE1pY3JvIERldmljZXMxDzANBgNVBAMTBkFNRFRQTTAeFw0xNDEwMjMxNDM0MzJaFw0zOTEwMjMxNDM0MzJaMHYxFDASBgNVBAsTC0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzESMBAGA1UEBxMJU3Vubnl2YWxlMQswCQYDVQQIEwJDQTEfMB0GA1UEChMWQWR2YW5jZWQgTWljcm8gRGV2aWNlczEPMA0GA1UEAxMGQU1EVFBNMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAssnOAYu5nRflQk0bVtsTFcLSAMx9odZ4Ey3n6/MA6FD7DECIE70RGZgaRIID0eb+dyX3znMrp1TS+lD+GJSw7yDJrKeU4it8cMLqFrqGm4SEx/X5GBa11sTmL4i60pJ5nDo2T69OiJ+iqYzgBfYJLqHQaeSRN6bBYyn3w1H4JNzPDNvqKHvkPfYewHjUAFJAI1dShYO8REnNCB8eeolj375nymfAAZzgA8v7zmFX/1tVLCy7Mm6n7zndT452TB1mek9LC5LkwlnyABwaN2Q8LV4NWpIAzTgr55xbU5VvgcIpw+/qcbYHmqL6ZzCSeE1gRKQXlsybK+W4phCtQfMgHQIDAQABo4IBEDCCAQwwDgYDVR0PAQH/BAQDAgEGMCMGCSsGAQQBgjcVKwQWBBRXjFRfeWlRQhIhpKV4rNtfaC+JyDAdBgNVHQ4EFgQUV4xUX3lpUUISIaSleKzbX2gvicgwDwYDVR0TAQH/BAUwAwEB/zA4BggrBgEFBQcBAQQsMCowKAYIKwYBBQUHMAGGHGh0dHA6Ly9mdHBtLmFtZC5jb20vcGtpL29jc3AwLAYDVR0fBCUwIzAhoB+gHYYbaHR0cDovL2Z0cG0uYW1kLmNvbS9wa2kvY3JsMD0GA1UdIAQ2MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxodHRwczovL2Z0cG0uYW1kLmNvbS9wa2kvY3BzMA0GCSqGSIb3DQEBCwUAA4IBAQCWB9yAoYYIt5HRY/OqJ5LUacP6rNmsMfPUDTcahXB3iQmY8HpUoGB23lhxbq+kz3vIiGAcUdKHlpB/epXyhABGTcJrNPMfx9akLqhI7WnMCPBbHDDDzKjjMB3Vm65PFbyuqbLujN/sN6kNtc4hL5r5Pr6Mze5H9WXBo2F2Oy+7+9jWMkxNrmUhoUUrF/6YsajTGPeq7r+i6q84W2nJdd+BoQQv4sk5GeuN2j2u4k1a8DkRPsVPc2I9QTtbzekchTK1GCXWki3DKGkZUEuaoaa60Kgw55Q5rt1eK7HKEG5npmR8aEod7BDLWy4CMTNAWR5iabCW/KX28JbJL6Phau9j`
20+
)
21+
22+
// mockClient is a mock implementation of httpClient for testing purposes.
23+
type MockClient struct {
24+
doFunc func(req *http.Request) (*http.Response, error)
25+
}
26+
27+
func (m *MockClient) Do(req *http.Request) (*http.Response, error) {
28+
if m.doFunc != nil {
29+
return m.doFunc(req)
30+
}
31+
return nil, errors.New("mocked doFunc not set")
32+
}
33+
34+
func NewDefaultDowloaderMockClient(t *testing.T) *MockClient {
35+
return NewDownloaderMockClient(t, DefaultDownloadTimeout)
36+
}
37+
38+
func NewDownloaderMockClient(t *testing.T, delay time.Duration) *MockClient {
39+
return NewDownloaderWithCRLMockClient(t, delay, nil, nil)
40+
}
41+
42+
func NewDownloaderWithCRLMockClient(t *testing.T, delay time.Duration, intelEKCRLMockResponse, intelEKCAMockResponse []byte) *MockClient {
43+
return &MockClient{
44+
doFunc: func(req *http.Request) (*http.Response, error) {
45+
select {
46+
case <-time.After(delay):
47+
switch {
48+
case req.URL.String() == "http://upgrades.intel.com/content/CRL/ekcert/SPTHEPIDPROD_EK_Device.crl":
49+
r := io.NopCloser(bytes.NewReader(intelEKCRLMockResponse))
50+
return &http.Response{
51+
StatusCode: 200,
52+
Body: r,
53+
}, nil
54+
case req.URL.String() == "http://upgrades.intel.com/content/CRL/ekcert/SPTHEPIDPROD_EK_Platform_Public_Key.cer":
55+
r := io.NopCloser(bytes.NewReader(intelEKCAMockResponse))
56+
return &http.Response{
57+
StatusCode: 200,
58+
Body: r,
59+
}, nil
60+
case req.URL.String() == "https://ekop.intel.com/ekcertservice/WVEG2rRwkQ7m3RpXlUphgo6Y2HLxl18h6ZZkkOAdnBE%3D":
61+
r := io.NopCloser(bytes.NewReader([]byte(intelEKMockResponse)))
62+
return &http.Response{
63+
StatusCode: 200,
64+
Body: r,
65+
}, nil
66+
case req.URL.String() == "https://ftpm.amd.com/pki/aia/264D39A23CEB5D5B49D610044EEBD121":
67+
b, err := base64.StdEncoding.DecodeString(amdEKRootMockResponseBase64)
68+
if err != nil {
69+
t.Fatalf("failed to decode AmdEKRootMockResponseBase64: %v", err)
70+
}
71+
r := io.NopCloser(bytes.NewReader(b))
72+
return &http.Response{
73+
StatusCode: 200,
74+
Body: r,
75+
}, nil
76+
case req.URL.String() == "http://pki/signer.cer":
77+
b, err := base64.StdEncoding.DecodeString(amdEKRootMockResponseBase64)
78+
if err != nil {
79+
t.Fatalf("failed to decode AmdEKRootMockResponseBase64: %v", err)
80+
}
81+
r := io.NopCloser(bytes.NewReader(b))
82+
return &http.Response{
83+
StatusCode: 200,
84+
Body: r,
85+
}, nil
86+
}
87+
return nil, errors.New("unexpected URL")
88+
case <-req.Context().Done():
89+
return nil, req.Context().Err()
90+
}
91+
},
92+
}
93+
}

0 commit comments

Comments
 (0)