forked from TrueCloudLab/certificates
Add attestationFormat property in the ACME provisioner
This commit is contained in:
parent
b2119e9f2c
commit
ba42aaf865
2 changed files with 129 additions and 6 deletions
|
@ -41,6 +41,38 @@ func (c ACMEChallenge) Validate() error {
|
|||
}
|
||||
}
|
||||
|
||||
// ACMEPlatform represents the format used on a device-attest-01 challenge.
|
||||
type ACMEAttestationFormat string
|
||||
|
||||
const (
|
||||
// APPLE is the format used to enable device-attest-01 on apple devices.
|
||||
APPLE ACMEAttestationFormat = "apple"
|
||||
|
||||
// STEP is the format used to enable device-attest-01 on devices that
|
||||
// provide attestation certificates like the PIV interface on YubiKeys.
|
||||
//
|
||||
// TODO(mariano): should we rename this to something else.
|
||||
STEP ACMEAttestationFormat = "step"
|
||||
|
||||
// TPM is the format used to enable device-attest-01 on TPMs.
|
||||
TPM ACMEAttestationFormat = "tpm"
|
||||
)
|
||||
|
||||
// String returns a normalized version of the attestation format.
|
||||
func (f ACMEAttestationFormat) String() string {
|
||||
return strings.ToLower(string(f))
|
||||
}
|
||||
|
||||
// Validate returns an error if the attestation format is not a valid one.
|
||||
func (f ACMEAttestationFormat) Validate() error {
|
||||
switch ACMEAttestationFormat(f.String()) {
|
||||
case APPLE, STEP, TPM:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("acme attestation format %q is not supported", f)
|
||||
}
|
||||
}
|
||||
|
||||
// ACME is the acme provisioner type, an entity that can authorize the ACME
|
||||
// provisioning flow.
|
||||
type ACME struct {
|
||||
|
@ -58,8 +90,12 @@ type ACME struct {
|
|||
// value is not set the default http-01, dns-01 and tls-alpn-01 challenges
|
||||
// will be enabled, device-attest-01 will be disabled.
|
||||
Challenges []ACMEChallenge `json:"challenges,omitempty"`
|
||||
Claims *Claims `json:"claims,omitempty"`
|
||||
Options *Options `json:"options,omitempty"`
|
||||
// AttestationFormats contains the enabled attestation formats for this
|
||||
// provisioner. If this value is not set the default apple, step and tpm
|
||||
// will be used.
|
||||
AttestationFormats []ACMEAttestationFormat `json:"attestationFormats,omitempty"`
|
||||
Claims *Claims `json:"claims,omitempty"`
|
||||
Options *Options `json:"options,omitempty"`
|
||||
|
||||
ctl *Controller
|
||||
}
|
||||
|
@ -123,6 +159,11 @@ func (p *ACME) Init(config Config) (err error) {
|
|||
return err
|
||||
}
|
||||
}
|
||||
for _, f := range p.AttestationFormats {
|
||||
if err := f.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
p.ctl, err = NewController(p, p.Claims, config, p.Options)
|
||||
return
|
||||
|
@ -222,3 +263,21 @@ func (p *ACME) IsChallengeEnabled(ctx context.Context, challenge ACMEChallenge)
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsAttestationFormatEnabled checks if the given attestation format is enabled.
|
||||
// By default apple, step and tpm are enabled, to disable any of them the
|
||||
// AttestationFormat provisioner property should have at least one element.
|
||||
func (p *ACME) IsAttestationFormatEnabled(ctx context.Context, format ACMEAttestationFormat) bool {
|
||||
enabledFormats := []ACMEAttestationFormat{
|
||||
APPLE, STEP, TPM,
|
||||
}
|
||||
if len(p.AttestationFormats) > 0 {
|
||||
enabledFormats = p.AttestationFormats
|
||||
}
|
||||
for _, f := range enabledFormats {
|
||||
if strings.EqualFold(string(f), string(format)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -35,6 +35,27 @@ func TestACMEChallenge_Validate(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestACMEAttestationFormat_Validate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
f ACMEAttestationFormat
|
||||
wantErr bool
|
||||
}{
|
||||
{"apple", APPLE, false},
|
||||
{"step", STEP, false},
|
||||
{"tpm", TPM, false},
|
||||
{"uppercase", "APPLE", false},
|
||||
{"fail", "FOO", true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := tt.f.Validate(); (err != nil) != tt.wantErr {
|
||||
t.Errorf("ACMEAttestationFormat.Validate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestACME_Getters(t *testing.T) {
|
||||
p, err := generateACME()
|
||||
assert.FatalError(t, err)
|
||||
|
@ -93,17 +114,24 @@ func TestACME_Init(t *testing.T) {
|
|||
err: errors.New("acme challenge \"zar\" is not supported"),
|
||||
}
|
||||
},
|
||||
"fail-bad-attestation-format": func(t *testing.T) ProvisionerValidateTest {
|
||||
return ProvisionerValidateTest{
|
||||
p: &ACME{Name: "foo", Type: "bar", AttestationFormats: []ACMEAttestationFormat{APPLE, "zar"}},
|
||||
err: errors.New("acme attestation format \"zar\" is not supported"),
|
||||
}
|
||||
},
|
||||
"ok": func(t *testing.T) ProvisionerValidateTest {
|
||||
return ProvisionerValidateTest{
|
||||
p: &ACME{Name: "foo", Type: "bar"},
|
||||
}
|
||||
},
|
||||
"ok with challenges": func(t *testing.T) ProvisionerValidateTest {
|
||||
"ok attestation": func(t *testing.T) ProvisionerValidateTest {
|
||||
return ProvisionerValidateTest{
|
||||
p: &ACME{
|
||||
Name: "foo",
|
||||
Type: "bar",
|
||||
Challenges: []ACMEChallenge{DNS_01, DEVICE_ATTEST_01},
|
||||
Name: "foo",
|
||||
Type: "bar",
|
||||
Challenges: []ACMEChallenge{DNS_01, DEVICE_ATTEST_01},
|
||||
AttestationFormats: []ACMEAttestationFormat{APPLE, STEP},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
@ -282,3 +310,39 @@ func TestACME_IsChallengeEnabled(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestACME_IsAttestationFormatEnabled(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
type fields struct {
|
||||
AttestationFormats []ACMEAttestationFormat
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
format ACMEAttestationFormat
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{"ok", fields{[]ACMEAttestationFormat{APPLE, STEP, TPM}}, args{ctx, TPM}, true},
|
||||
{"ok empty apple", fields{nil}, args{ctx, APPLE}, true},
|
||||
{"ok empty step", fields{nil}, args{ctx, STEP}, true},
|
||||
{"ok empty tpm", fields{[]ACMEAttestationFormat{}}, args{ctx, "tpm"}, true},
|
||||
{"ok uppercase", fields{[]ACMEAttestationFormat{APPLE, STEP, TPM}}, args{ctx, "STEP"}, true},
|
||||
{"fail apple", fields{[]ACMEAttestationFormat{STEP, TPM}}, args{ctx, APPLE}, false},
|
||||
{"fail step", fields{[]ACMEAttestationFormat{APPLE, TPM}}, args{ctx, STEP}, false},
|
||||
{"fail step", fields{[]ACMEAttestationFormat{APPLE, STEP}}, args{ctx, TPM}, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
p := &ACME{
|
||||
AttestationFormats: tt.fields.AttestationFormats,
|
||||
}
|
||||
if got := p.IsAttestationFormatEnabled(tt.args.ctx, tt.args.format); got != tt.want {
|
||||
t.Errorf("ACME.IsAttestationFormatEnabled() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue