Merge branch 'master' into herman/improve-scep-marshaling
commit
f9ec62f46c
@ -0,0 +1,22 @@
|
||||
name: Dependabot auto-merge
|
||||
on: pull_request
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
dependabot:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.actor == 'dependabot[bot]' }}
|
||||
steps:
|
||||
- name: Dependabot metadata
|
||||
id: metadata
|
||||
uses: dependabot/fetch-metadata@v1.1.1
|
||||
with:
|
||||
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
- name: Enable auto-merge for Dependabot PRs
|
||||
run: gh pr merge --auto --merge "$PR_URL"
|
||||
env:
|
||||
PR_URL: ${{github.event.pull_request.html_url}}
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,859 @@
|
||||
//go:build tpmsimulator
|
||||
// +build tpmsimulator
|
||||
|
||||
package acme
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/fxamacker/cbor/v2"
|
||||
"github.com/google/go-attestation/attest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/keyutil"
|
||||
"go.step.sm/crypto/minica"
|
||||
"go.step.sm/crypto/tpm"
|
||||
"go.step.sm/crypto/tpm/simulator"
|
||||
tpmstorage "go.step.sm/crypto/tpm/storage"
|
||||
"go.step.sm/crypto/x509util"
|
||||
)
|
||||
|
||||
func newSimulatedTPM(t *testing.T) *tpm.TPM {
|
||||
t.Helper()
|
||||
tmpDir := t.TempDir()
|
||||
tpm, err := tpm.New(withSimulator(t), tpm.WithStore(tpmstorage.NewDirstore(tmpDir))) // TODO: provide in-memory storage implementation instead
|
||||
require.NoError(t, err)
|
||||
return tpm
|
||||
}
|
||||
|
||||
func withSimulator(t *testing.T) tpm.NewTPMOption {
|
||||
t.Helper()
|
||||
var sim simulator.Simulator
|
||||
t.Cleanup(func() {
|
||||
if sim == nil {
|
||||
return
|
||||
}
|
||||
err := sim.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
sim = simulator.New()
|
||||
err := sim.Open()
|
||||
require.NoError(t, err)
|
||||
return tpm.WithSimulator(sim)
|
||||
}
|
||||
|
||||
func generateKeyID(t *testing.T, pub crypto.PublicKey) []byte {
|
||||
t.Helper()
|
||||
b, err := x509.MarshalPKIXPublicKey(pub)
|
||||
require.NoError(t, err)
|
||||
hash := sha256.Sum256(b)
|
||||
return hash[:]
|
||||
}
|
||||
|
||||
func mustAttestTPM(t *testing.T, keyAuthorization string, permanentIdentifiers []string) ([]byte, crypto.Signer, *x509.Certificate) {
|
||||
t.Helper()
|
||||
aca, err := minica.New(
|
||||
minica.WithName("TPM Testing"),
|
||||
minica.WithGetSignerFunc(
|
||||
func() (crypto.Signer, error) {
|
||||
return keyutil.GenerateSigner("RSA", "", 2048)
|
||||
},
|
||||
),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// prepare simulated TPM and create an AK
|
||||
stpm := newSimulatedTPM(t)
|
||||
eks, err := stpm.GetEKs(context.Background())
|
||||
require.NoError(t, err)
|
||||
ak, err := stpm.CreateAK(context.Background(), "first-ak")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ak)
|
||||
|
||||
// extract the AK public key // TODO(hs): replace this when there's a simpler method to get the AK public key (e.g. ak.Public())
|
||||
ap, err := ak.AttestationParameters(context.Background())
|
||||
require.NoError(t, err)
|
||||
akp, err := attest.ParseAKPublic(attest.TPMVersion20, ap.Public)
|
||||
require.NoError(t, err)
|
||||
|
||||
// create template and sign certificate for the AK public key
|
||||
keyID := generateKeyID(t, eks[0].Public())
|
||||
template := &x509.Certificate{
|
||||
PublicKey: akp.Public,
|
||||
IsCA: false,
|
||||
UnknownExtKeyUsage: []asn1.ObjectIdentifier{oidTCGKpAIKCertificate},
|
||||
}
|
||||
sans := []x509util.SubjectAlternativeName{}
|
||||
uris := []*url.URL{{Scheme: "urn", Opaque: "ek:sha256:" + base64.StdEncoding.EncodeToString(keyID)}}
|
||||
for _, pi := range permanentIdentifiers {
|
||||
sans = append(sans, x509util.SubjectAlternativeName{
|
||||
Type: x509util.PermanentIdentifierType,
|
||||
Value: pi,
|
||||
})
|
||||
}
|
||||
asn1Value := []byte(fmt.Sprintf(`{"extraNames":[{"type": %q, "value": %q},{"type": %q, "value": %q},{"type": %q, "value": %q}]}`, oidTPMManufacturer, "1414747215", oidTPMModel, "SLB 9670 TPM2.0", oidTPMVersion, "7.55"))
|
||||
sans = append(sans, x509util.SubjectAlternativeName{
|
||||
Type: x509util.DirectoryNameType,
|
||||
ASN1Value: asn1Value,
|
||||
})
|
||||
ext, err := createSubjectAltNameExtension(nil, nil, nil, uris, sans, true)
|
||||
require.NoError(t, err)
|
||||
ext.Set(template)
|
||||
akCert, err := aca.Sign(template)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, akCert)
|
||||
|
||||
// create a new key attested by the AK, while including
|
||||
// the key authorization bytes as qualifying data.
|
||||
keyAuthSum := sha256.Sum256([]byte(keyAuthorization))
|
||||
config := tpm.AttestKeyConfig{
|
||||
Algorithm: "RSA",
|
||||
Size: 2048,
|
||||
QualifyingData: keyAuthSum[:],
|
||||
}
|
||||
key, err := stpm.AttestKey(context.Background(), "first-ak", "first-key", config)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, key)
|
||||
require.Equal(t, "first-key", key.Name())
|
||||
require.NotEqual(t, 0, len(key.Data()))
|
||||
require.Equal(t, "first-ak", key.AttestedBy())
|
||||
require.True(t, key.WasAttested())
|
||||
require.True(t, key.WasAttestedBy(ak))
|
||||
|
||||
signer, err := key.Signer(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
// prepare the attestation object with the AK certificate chain,
|
||||
// the attested key, its metadata and the signature signed by the
|
||||
// AK.
|
||||
params, err := key.CertificationParameters(context.Background())
|
||||
require.NoError(t, err)
|
||||
attObj, err := cbor.Marshal(struct {
|
||||
Format string `json:"fmt"`
|
||||
AttStatement map[string]interface{} `json:"attStmt,omitempty"`
|
||||
}{
|
||||
Format: "tpm",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||
"alg": int64(-257), // RS256
|
||||
"sig": params.CreateSignature,
|
||||
"certInfo": params.CreateAttestation,
|
||||
"pubArea": params.Public,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// marshal the ACME payload
|
||||
payload, err := json.Marshal(struct {
|
||||
AttObj string `json:"attObj"`
|
||||
}{
|
||||
AttObj: base64.RawURLEncoding.EncodeToString(attObj),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return payload, signer, aca.Root
|
||||
}
|
||||
|
||||
func Test_deviceAttest01ValidateWithTPMSimulator(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
ch *Challenge
|
||||
db DB
|
||||
jwk *jose.JSONWebKey
|
||||
payload []byte
|
||||
}
|
||||
type test struct {
|
||||
args args
|
||||
wantErr *Error
|
||||
}
|
||||
tests := map[string]func(t *testing.T) test{
|
||||
"ok/doTPMAttestationFormat-storeError": func(t *testing.T) test {
|
||||
jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token")
|
||||
payload, _, root := mustAttestTPM(t, keyAuth, nil) // TODO: value(s) for AK cert?
|
||||
caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: root.Raw})
|
||||
ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, caRoot))
|
||||
|
||||
// parse payload, set invalid "ver", remarshal
|
||||
var p payloadType
|
||||
err := json.Unmarshal(payload, &p)
|
||||
require.NoError(t, err)
|
||||
attObj, err := base64.RawURLEncoding.DecodeString(p.AttObj)
|
||||
require.NoError(t, err)
|
||||
att := attestationObject{}
|
||||
err = cbor.Unmarshal(attObj, &att)
|
||||
require.NoError(t, err)
|
||||
att.AttStatement["ver"] = "bogus"
|
||||
attObj, err = cbor.Marshal(struct {
|
||||
Format string `json:"fmt"`
|
||||
AttStatement map[string]interface{} `json:"attStmt,omitempty"`
|
||||
}{
|
||||
Format: "tpm",
|
||||
AttStatement: att.AttStatement,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
payload, err = json.Marshal(struct {
|
||||
AttObj string `json:"attObj"`
|
||||
}{
|
||||
AttObj: base64.RawURLEncoding.EncodeToString(attObj),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return test{
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
jwk: jwk,
|
||||
ch: &Challenge{
|
||||
ID: "chID",
|
||||
AuthorizationID: "azID",
|
||||
Token: "token",
|
||||
Type: "device-attest-01",
|
||||
Status: StatusPending,
|
||||
Value: "device.id.12345678",
|
||||
},
|
||||
payload: payload,
|
||||
db: &MockDB{
|
||||
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
|
||||
assert.Equal(t, "azID", id)
|
||||
return &Authorization{ID: "azID"}, nil
|
||||
},
|
||||
MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
|
||||
assert.Equal(t, "chID", updch.ID)
|
||||
assert.Equal(t, "token", updch.Token)
|
||||
assert.Equal(t, StatusInvalid, updch.Status)
|
||||
assert.Equal(t, ChallengeType("device-attest-01"), updch.Type)
|
||||
assert.Equal(t, "device.id.12345678", updch.Value)
|
||||
|
||||
err := NewError(ErrorBadAttestationStatementType, `version "bogus" is not supported`)
|
||||
|
||||
assert.EqualError(t, updch.Error.Err, err.Err.Error())
|
||||
assert.Equal(t, err.Type, updch.Error.Type)
|
||||
assert.Equal(t, err.Detail, updch.Error.Detail)
|
||||
assert.Equal(t, err.Status, updch.Error.Status)
|
||||
assert.Equal(t, err.Subproblems, updch.Error.Subproblems)
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: nil,
|
||||
}
|
||||
},
|
||||
"ok with invalid PermanentIdentifier SAN": func(t *testing.T) test {
|
||||
jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token")
|
||||
payload, _, root := mustAttestTPM(t, keyAuth, []string{"device.id.12345678"}) // TODO: value(s) for AK cert?
|
||||
caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: root.Raw})
|
||||
ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, caRoot))
|
||||
return test{
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
jwk: jwk,
|
||||
ch: &Challenge{
|
||||
ID: "chID",
|
||||
AuthorizationID: "azID",
|
||||
Token: "token",
|
||||
Type: "device-attest-01",
|
||||
Status: StatusPending,
|
||||
Value: "device.id.99999999",
|
||||
},
|
||||
payload: payload,
|
||||
db: &MockDB{
|
||||
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
|
||||
assert.Equal(t, "azID", id)
|
||||
return &Authorization{ID: "azID"}, nil
|
||||
},
|
||||
MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
|
||||
assert.Equal(t, "chID", updch.ID)
|
||||
assert.Equal(t, "token", updch.Token)
|
||||
assert.Equal(t, StatusInvalid, updch.Status)
|
||||
assert.Equal(t, ChallengeType("device-attest-01"), updch.Type)
|
||||
assert.Equal(t, "device.id.99999999", updch.Value)
|
||||
|
||||
err := NewError(ErrorRejectedIdentifierType, `permanent identifier does not match`).
|
||||
AddSubproblems(NewSubproblemWithIdentifier(
|
||||
ErrorMalformedType,
|
||||
Identifier{Type: "permanent-identifier", Value: "device.id.99999999"},
|
||||
`challenge identifier "device.id.99999999" doesn't match any of the attested hardware identifiers ["device.id.12345678"]`,
|
||||
))
|
||||
|
||||
assert.EqualError(t, updch.Error.Err, err.Err.Error())
|
||||
assert.Equal(t, err.Type, updch.Error.Type)
|
||||
assert.Equal(t, err.Detail, updch.Error.Detail)
|
||||
assert.Equal(t, err.Status, updch.Error.Status)
|
||||
assert.Equal(t, err.Subproblems, updch.Error.Subproblems)
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: nil,
|
||||
}
|
||||
},
|
||||
"ok": func(t *testing.T) test {
|
||||
jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token")
|
||||
payload, signer, root := mustAttestTPM(t, keyAuth, nil) // TODO: value(s) for AK cert?
|
||||
caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: root.Raw})
|
||||
ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, caRoot))
|
||||
return test{
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
jwk: jwk,
|
||||
ch: &Challenge{
|
||||
ID: "chID",
|
||||
AuthorizationID: "azID",
|
||||
Token: "token",
|
||||
Type: "device-attest-01",
|
||||
Status: StatusPending,
|
||||
Value: "device.id.12345678",
|
||||
},
|
||||
payload: payload,
|
||||
db: &MockDB{
|
||||
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
|
||||
assert.Equal(t, "azID", id)
|
||||
return &Authorization{ID: "azID"}, nil
|
||||
},
|
||||
MockUpdateAuthorization: func(ctx context.Context, az *Authorization) error {
|
||||
fingerprint, err := keyutil.Fingerprint(signer.Public())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "azID", az.ID)
|
||||
assert.Equal(t, fingerprint, az.Fingerprint)
|
||||
return nil
|
||||
},
|
||||
MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
|
||||
assert.Equal(t, "chID", updch.ID)
|
||||
assert.Equal(t, "token", updch.Token)
|
||||
assert.Equal(t, StatusValid, updch.Status)
|
||||
assert.Equal(t, ChallengeType("device-attest-01"), updch.Type)
|
||||
assert.Equal(t, "device.id.12345678", updch.Value)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: nil,
|
||||
}
|
||||
},
|
||||
"ok with PermanentIdentifier SAN": func(t *testing.T) test {
|
||||
jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token")
|
||||
payload, signer, root := mustAttestTPM(t, keyAuth, []string{"device.id.12345678"}) // TODO: value(s) for AK cert?
|
||||
caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: root.Raw})
|
||||
ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, caRoot))
|
||||
return test{
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
jwk: jwk,
|
||||
ch: &Challenge{
|
||||
ID: "chID",
|
||||
AuthorizationID: "azID",
|
||||
Token: "token",
|
||||
Type: "device-attest-01",
|
||||
Status: StatusPending,
|
||||
Value: "device.id.12345678",
|
||||
},
|
||||
payload: payload,
|
||||
db: &MockDB{
|
||||
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
|
||||
assert.Equal(t, "azID", id)
|
||||
return &Authorization{ID: "azID"}, nil
|
||||
},
|
||||
MockUpdateAuthorization: func(ctx context.Context, az *Authorization) error {
|
||||
fingerprint, err := keyutil.Fingerprint(signer.Public())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "azID", az.ID)
|
||||
assert.Equal(t, fingerprint, az.Fingerprint)
|
||||
return nil
|
||||
},
|
||||
MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
|
||||
assert.Equal(t, "chID", updch.ID)
|
||||
assert.Equal(t, "token", updch.Token)
|
||||
assert.Equal(t, StatusValid, updch.Status)
|
||||
assert.Equal(t, ChallengeType("device-attest-01"), updch.Type)
|
||||
assert.Equal(t, "device.id.12345678", updch.Value)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: nil,
|
||||
}
|
||||
},
|
||||
}
|
||||
for name, run := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tc := run(t)
|
||||
|
||||
if err := deviceAttest01Validate(tc.args.ctx, tc.args.ch, tc.args.db, tc.args.jwk, tc.args.payload); err != nil {
|
||||
assert.Error(t, tc.wantErr)
|
||||
assert.EqualError(t, err, tc.wantErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
assert.Nil(t, tc.wantErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newBadAttestationStatementError(msg string) *Error {
|
||||
return &Error{
|
||||
Type: "urn:ietf:params:acme:error:badAttestationStatement",
|
||||
Status: 400,
|
||||
Err: errors.New(msg),
|
||||
}
|
||||
}
|
||||
|
||||
func newInternalServerError(msg string) *Error {
|
||||
return &Error{
|
||||
Type: "urn:ietf:params:acme:error:serverInternal",
|
||||
Status: 500,
|
||||
Err: errors.New(msg),
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
oidPermanentIdentifier = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 8, 3}
|
||||
oidHardwareModuleNameIdentifier = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 8, 4}
|
||||
)
|
||||
|
||||
func Test_doTPMAttestationFormat(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
aca, err := minica.New(
|
||||
minica.WithName("TPM Testing"),
|
||||
minica.WithGetSignerFunc(
|
||||
func() (crypto.Signer, error) {
|
||||
return keyutil.GenerateSigner("RSA", "", 2048)
|
||||
},
|
||||
),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
acaRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: aca.Root.Raw})
|
||||
|
||||
// prepare simulated TPM and create an AK
|
||||
stpm := newSimulatedTPM(t)
|
||||
eks, err := stpm.GetEKs(context.Background())
|
||||
require.NoError(t, err)
|
||||
ak, err := stpm.CreateAK(context.Background(), "first-ak")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ak)
|
||||
|
||||
// extract the AK public key // TODO(hs): replace this when there's a simpler method to get the AK public key (e.g. ak.Public())
|
||||
ap, err := ak.AttestationParameters(context.Background())
|
||||
require.NoError(t, err)
|
||||
akp, err := attest.ParseAKPublic(attest.TPMVersion20, ap.Public)
|
||||
require.NoError(t, err)
|
||||
|
||||
// create template and sign certificate for the AK public key
|
||||
keyID := generateKeyID(t, eks[0].Public())
|
||||
template := &x509.Certificate{
|
||||
PublicKey: akp.Public,
|
||||
IsCA: false,
|
||||
UnknownExtKeyUsage: []asn1.ObjectIdentifier{oidTCGKpAIKCertificate},
|
||||
}
|
||||
sans := []x509util.SubjectAlternativeName{}
|
||||
uris := []*url.URL{{Scheme: "urn", Opaque: "ek:sha256:" + base64.StdEncoding.EncodeToString(keyID)}}
|
||||
asn1Value := []byte(fmt.Sprintf(`{"extraNames":[{"type": %q, "value": %q},{"type": %q, "value": %q},{"type": %q, "value": %q}]}`, oidTPMManufacturer, "1414747215", oidTPMModel, "SLB 9670 TPM2.0", oidTPMVersion, "7.55"))
|
||||
sans = append(sans, x509util.SubjectAlternativeName{
|
||||
Type: x509util.DirectoryNameType,
|
||||
ASN1Value: asn1Value,
|
||||
})
|
||||
ext, err := createSubjectAltNameExtension(nil, nil, nil, uris, sans, true)
|
||||
require.NoError(t, err)
|
||||
ext.Set(template)
|
||||
akCert, err := aca.Sign(template)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, akCert)
|
||||
|
||||
invalidTemplate := &x509.Certificate{
|
||||
PublicKey: akp.Public,
|
||||
IsCA: false,
|
||||
UnknownExtKeyUsage: []asn1.ObjectIdentifier{oidTCGKpAIKCertificate},
|
||||
}
|
||||
invalidAKCert, err := aca.Sign(invalidTemplate)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, invalidAKCert)
|
||||
|
||||
// generate a JWK and the key authorization value
|
||||
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
||||
require.NoError(t, err)
|
||||
keyAuthorization, err := KeyAuthorization("token", jwk)
|
||||
require.NoError(t, err)
|
||||
|
||||
// create a new key attested by the AK, while including
|
||||
// the key authorization bytes as qualifying data.
|
||||
keyAuthSum := sha256.Sum256([]byte(keyAuthorization))
|
||||
config := tpm.AttestKeyConfig{
|
||||
Algorithm: "RSA",
|
||||
Size: 2048,
|
||||
QualifyingData: keyAuthSum[:],
|
||||
}
|
||||
key, err := stpm.AttestKey(context.Background(), "first-ak", "first-key", config)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, key)
|
||||
params, err := key.CertificationParameters(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
signer, err := key.Signer(context.Background())
|
||||
require.NoError(t, err)
|
||||
fingerprint, err := keyutil.Fingerprint(signer.Public())
|
||||
require.NoError(t, err)
|
||||
|
||||
// attest another key and get its certification parameters
|
||||
anotherKey, err := stpm.AttestKey(context.Background(), "first-ak", "another-key", config)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, key)
|
||||
anotherKeyParams, err := anotherKey.CertificationParameters(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
prov Provisioner
|
||||
ch *Challenge
|
||||
jwk *jose.JSONWebKey
|
||||
att *attestationObject
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *tpmAttestationData
|
||||
expErr *Error
|
||||
}{
|
||||
{"ok", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "tpm",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||
"alg": int64(-257), // RS256
|
||||
"sig": params.CreateSignature,
|
||||
"certInfo": params.CreateAttestation,
|
||||
"pubArea": params.Public,
|
||||
},
|
||||
}}, nil, nil},
|
||||
{"fail ver not present", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "tpm",
|
||||
AttStatement: map[string]interface{}{
|
||||
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||
"alg": int64(-257), // RS256
|
||||
"sig": params.CreateSignature,
|
||||
"certInfo": params.CreateAttestation,
|
||||
"pubArea": params.Public,
|
||||
},
|
||||
}}, nil, newBadAttestationStatementError("ver not present")},
|
||||
{"fail ver type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "tpm",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": []interface{}{},
|
||||
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||
"alg": int64(-257), // RS256
|
||||
"sig": params.CreateSignature,
|
||||
"certInfo": params.CreateAttestation,
|
||||
"pubArea": params.Public,
|
||||
},
|
||||
}}, nil, newBadAttestationStatementError("ver not present")},
|
||||
{"fail bogus ver", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "tpm",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "bogus",
|
||||
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||
"alg": int64(-257), // RS256
|
||||
"sig": params.CreateSignature,
|
||||
"certInfo": params.CreateAttestation,
|
||||
"pubArea": params.Public,
|
||||
},
|
||||
}}, nil, newBadAttestationStatementError(`version "bogus" is not supported`)},
|
||||
{"fail x5c not present", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "tpm",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"alg": int64(-257), // RS256
|
||||
"sig": params.CreateSignature,
|
||||
"certInfo": params.CreateAttestation,
|
||||
"pubArea": params.Public,
|
||||
},
|
||||
}}, nil, newBadAttestationStatementError("x5c not present")},
|
||||
{"fail x5c type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "tpm",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"x5c": [][]byte{akCert.Raw, aca.Intermediate.Raw},
|
||||
"alg": int64(-257), // RS256
|
||||
"sig": params.CreateSignature,
|
||||
"certInfo": params.CreateAttestation,
|
||||
"pubArea": params.Public,
|
||||
},
|
||||
}}, nil, newBadAttestationStatementError("x5c not present")},
|
||||
{"fail x5c empty", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "tpm",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"x5c": []interface{}{},
|
||||
"alg": int64(-257), // RS256
|
||||
"sig": params.CreateSignature,
|
||||
"certInfo": params.CreateAttestation,
|
||||
"pubArea": params.Public,
|
||||
},
|
||||
}}, nil, newBadAttestationStatementError("x5c is empty")},
|
||||
{"fail leaf type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "step",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"x5c": []interface{}{"leaf", aca.Intermediate.Raw},
|
||||
"alg": int64(-257), // RS256
|
||||
"sig": params.CreateSignature,
|
||||
"certInfo": params.CreateAttestation,
|
||||
"pubArea": params.Public,
|
||||
},
|
||||
}}, nil, newBadAttestationStatementError("x5c is malformed")},
|
||||
{"fail leaf parse", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "step",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"x5c": []interface{}{akCert.Raw[:100], aca.Intermediate.Raw},
|
||||
"alg": int64(-257), // RS256
|
||||
"sig": params.CreateSignature,
|
||||
"certInfo": params.CreateAttestation,
|
||||
"pubArea": params.Public,
|
||||
},
|
||||
}}, nil, newBadAttestationStatementError("x5c is malformed: x509: malformed certificate")},
|
||||
{"fail intermediate type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "step",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"x5c": []interface{}{akCert.Raw, "intermediate"},
|
||||
"alg": int64(-257), // RS256
|
||||
"sig": params.CreateSignature,
|
||||
"certInfo": params.CreateAttestation,
|
||||
"pubArea": params.Public,
|
||||
},
|
||||
}}, nil, newBadAttestationStatementError("x5c is malformed")},
|
||||
{"fail intermediate parse", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "step",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw[:100]},
|
||||
"alg": int64(-257), // RS256
|
||||
"sig": params.CreateSignature,
|
||||
"certInfo": params.CreateAttestation,
|
||||
"pubArea": params.Public,
|
||||
},
|
||||
}}, nil, newBadAttestationStatementError("x5c is malformed: x509: malformed certificate")},
|
||||
{"fail roots", args{ctx, mustAttestationProvisioner(t, nil), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "tpm",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||
"alg": int64(-257), // RS256
|
||||
"sig": params.CreateSignature,
|
||||
"certInfo": params.CreateAttestation,
|
||||
"pubArea": params.Public,
|
||||
},
|
||||
}}, nil, newInternalServerError("no root CA bundle available to verify the attestation certificate")},
|
||||
{"fail verify", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "step",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"x5c": []interface{}{akCert.Raw},
|
||||
"alg": int64(-257), // RS256
|
||||
"sig": params.CreateSignature,
|
||||
"certInfo": params.CreateAttestation,
|
||||
"pubArea": params.Public,
|
||||
},
|
||||
}}, nil, newBadAttestationStatementError("x5c is not valid: x509: certificate signed by unknown authority")},
|
||||
{"fail validateAKCertificate", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "tpm",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"x5c": []interface{}{invalidAKCert.Raw, aca.Intermediate.Raw},
|
||||
"alg": int64(-257), // RS256
|
||||
"sig": params.CreateSignature,
|
||||
"certInfo": params.CreateAttestation,
|
||||
"pubArea": params.Public,
|
||||
},
|
||||
}}, nil, newBadAttestationStatementError("AK certificate is not valid: missing TPM manufacturer")},
|
||||
{"fail pubArea not present", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "tpm",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||
"alg": int64(-257), // RS256
|
||||
"sig": params.CreateSignature,
|
||||
"certInfo": params.CreateAttestation,
|
||||
},
|
||||
}}, nil, newBadAttestationStatementError("invalid pubArea in attestation statement")},
|
||||
{"fail pubArea type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "tpm",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||
"alg": int64(-257), // RS256
|
||||
"sig": params.CreateSignature,
|
||||
"certInfo": params.CreateAttestation,
|
||||
"pubArea": []interface{}{},
|
||||
},
|
||||
}}, nil, newBadAttestationStatementError("invalid pubArea in attestation statement")},
|
||||
{"fail pubArea empty", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "tpm",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||
"alg": int64(-257), // RS256
|
||||
"sig": params.CreateSignature,
|
||||
"certInfo": params.CreateAttestation,
|
||||
"pubArea": []byte{},
|
||||
},
|
||||
}}, nil, newBadAttestationStatementError("pubArea is empty")},
|
||||
{"fail sig not present", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "tpm",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||
"alg": int64(-257), // RS256
|
||||
"certInfo": params.CreateAttestation,
|
||||
"pubArea": params.Public,
|
||||
},
|
||||
}}, nil, newBadAttestationStatementError("invalid sig in attestation statement")},
|
||||
{"fail sig type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "tpm",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||
"alg": int64(-257), // RS256
|
||||
"sig": []interface{}{},
|
||||
"certInfo": params.CreateAttestation,
|
||||
"pubArea": params.Public,
|
||||
},
|
||||
}}, nil, newBadAttestationStatementError("invalid sig in attestation statement")},
|
||||
{"fail sig empty", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "tpm",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||
"alg": int64(-257), // RS256
|
||||
"sig": []byte{},
|
||||
"certInfo": params.CreateAttestation,
|
||||
"pubArea": params.Public,
|
||||
},
|
||||
}}, nil, newBadAttestationStatementError("sig is empty")},
|
||||
{"fail certInfo not present", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "tpm",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||
"alg": int64(-257), // RS256
|
||||
"sig": params.CreateSignature,
|
||||
"pubArea": params.Public,
|
||||
},
|
||||
}}, nil, newBadAttestationStatementError("invalid certInfo in attestation statement")},
|
||||
{"fail certInfo type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "tpm",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||
"alg": int64(-257), // RS256
|
||||
"sig": params.CreateSignature,
|
||||
"certInfo": []interface{}{},
|
||||
"pubArea": params.Public,
|
||||
},
|
||||
}}, nil, newBadAttestationStatementError("invalid certInfo in attestation statement")},
|
||||
{"fail certInfo empty", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "tpm",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||
"alg": int64(-257), // RS256
|
||||
"sig": params.CreateSignature,
|
||||
"certInfo": []byte{},
|
||||
"pubArea": params.Public,
|
||||
},
|
||||
}}, nil, newBadAttestationStatementError("certInfo is empty")},
|
||||
{"fail alg not present", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "tpm",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||
"sig": params.CreateSignature,
|
||||
"certInfo": params.CreateAttestation,
|
||||
"pubArea": params.Public,
|
||||
},
|
||||
}}, nil, newBadAttestationStatementError("invalid alg in attestation statement")},
|
||||
{"fail alg type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "tpm",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||
"alg": int64(0), // invalid alg
|
||||
"sig": params.CreateSignature,
|
||||
"certInfo": params.CreateAttestation,
|
||||
"pubArea": params.Public,
|
||||
},
|
||||
}}, nil, newBadAttestationStatementError("invalid alg 0 in attestation statement")},
|
||||
{"fail attestation verification", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||
Format: "tpm",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||
"alg": int64(-257), // RS256
|
||||
"sig": params.CreateSignature,
|
||||
"certInfo": params.CreateAttestation,
|
||||
"pubArea": anotherKeyParams.Public,
|
||||
},
|
||||
}}, nil, newBadAttestationStatementError("invalid certification parameters: certification refers to a different key")},
|
||||
{"fail keyAuthorization", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, &jose.JSONWebKey{Key: []byte("not an asymmetric key")}, &attestationObject{
|
||||
Format: "tpm",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||
"alg": int64(-257), // RS256
|
||||
"sig": params.CreateSignature,
|
||||
"certInfo": params.CreateAttestation,
|
||||
"pubArea": params.Public,
|
||||
},
|
||||
}}, nil, newInternalServerError("failed creating key auth digest: error generating JWK thumbprint: square/go-jose: unknown key type '[]uint8'")},
|
||||
{"fail different keyAuthorization", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "aDifferentToken"}, jwk, &attestationObject{
|
||||
Format: "tpm",
|
||||
AttStatement: map[string]interface{}{
|
||||
"ver": "2.0",
|
||||
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||
"alg": int64(-257), //
|
||||
"sig": params.CreateSignature,
|
||||
"certInfo": params.CreateAttestation,
|
||||
"pubArea": params.Public,
|
||||
},
|
||||
}}, nil, newBadAttestationStatementError("key authorization does not match")},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := doTPMAttestationFormat(tt.args.ctx, tt.args.prov, tt.args.ch, tt.args.jwk, tt.args.att)
|
||||
if tt.expErr != nil {
|
||||
var ae *Error
|
||||
if assert.True(t, errors.As(err, &ae)) {
|
||||
assert.EqualError(t, err, tt.expErr.Error())
|
||||
assert.Equal(t, ae.StatusCode(), tt.expErr.StatusCode())
|
||||
assert.Equal(t, ae.Type, tt.expErr.Type)
|
||||
}
|
||||
assert.Nil(t, got)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
if assert.NotNil(t, got) {
|
||||
assert.Equal(t, akCert, got.Certificate)
|
||||
assert.Equal(t, [][]*x509.Certificate{
|
||||
{
|
||||
akCert, aca.Intermediate, aca.Root,
|
||||
},
|
||||
}, got.VerifiedChains)
|
||||
assert.Equal(t, fingerprint, got.Fingerprint)
|
||||
assert.Empty(t, got.PermanentIdentifiers) // currently expected to be always empty
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,342 @@
|
||||
package provisioner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.step.sm/linkedca"
|
||||
)
|
||||
|
||||
func Test_challengeValidationController_Validate(t *testing.T) {
|
||||
type request struct {
|
||||
Challenge string `json:"scepChallenge"`
|
||||
TransactionID string `json:"scepTransactionID"`
|
||||
}
|
||||
type response struct {
|
||||
Allow bool `json:"allow"`
|
||||
}
|
||||
nokServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
req := &request{}
|
||||
err := json.NewDecoder(r.Body).Decode(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "not-allowed", req.Challenge)
|
||||
assert.Equal(t, "transaction-1", req.TransactionID)
|
||||
b, err := json.Marshal(response{Allow: false})
|
||||
require.NoError(t, err)
|
||||
w.WriteHeader(200)
|
||||
w.Write(b)
|
||||
}))
|
||||
okServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
req := &request{}
|
||||
err := json.NewDecoder(r.Body).Decode(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "challenge", req.Challenge)
|
||||
assert.Equal(t, "transaction-1", req.TransactionID)
|
||||
b, err := json.Marshal(response{Allow: true})
|
||||
require.NoError(t, err)
|
||||
w.WriteHeader(200)
|
||||
w.Write(b)
|
||||
}))
|
||||
type fields struct {
|
||||
client *http.Client
|
||||
webhooks []*Webhook
|
||||
}
|
||||
type args struct {
|
||||
challenge string
|
||||
transactionID string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
server *httptest.Server
|
||||
expErr error
|
||||
}{
|
||||
{
|
||||
name: "fail/no-webhook",
|
||||
fields: fields{http.DefaultClient, nil},
|
||||
args: args{"no-webhook", "transaction-1"},
|
||||
expErr: errors.New("webhook server did not allow request"),
|
||||
},
|
||||
{
|
||||
name: "fail/wrong-cert-type",
|
||||
fields: fields{http.DefaultClient, []*Webhook{
|
||||
{
|
||||
Kind: linkedca.Webhook_SCEPCHALLENGE.String(),
|
||||
CertType: linkedca.Webhook_SSH.String(),
|
||||
},
|
||||
}},
|
||||
args: args{"wrong-cert-type", "transaction-1"},
|
||||
expErr: errors.New("webhook server did not allow request"),
|
||||
},
|
||||
{
|
||||
name: "fail/wrong-secret-value",
|
||||
fields: fields{http.DefaultClient, []*Webhook{
|
||||
{
|
||||
ID: "webhook-id-1",
|
||||
Name: "webhook-name-1",
|
||||
Secret: "{{}}",
|
||||
Kind: linkedca.Webhook_SCEPCHALLENGE.String(),
|
||||
CertType: linkedca.Webhook_X509.String(),
|
||||
URL: okServer.URL,
|
||||
},
|
||||
}},
|
||||
args: args{
|
||||
challenge: "wrong-secret-value",
|
||||
transactionID: "transaction-1",
|
||||
},
|
||||
expErr: errors.New("failed executing webhook request: illegal base64 data at input byte 0"),
|
||||
},
|
||||
{
|
||||
name: "fail/not-allowed",
|
||||
fields: fields{http.DefaultClient, []*Webhook{
|
||||
{
|
||||
ID: "webhook-id-1",
|
||||
Name: "webhook-name-1",
|
||||
Secret: "MTIzNAo=",
|
||||
Kind: linkedca.Webhook_SCEPCHALLENGE.String(),
|
||||
CertType: linkedca.Webhook_X509.String(),
|
||||
URL: nokServer.URL,
|
||||
},
|
||||
}},
|
||||
args: args{
|
||||
challenge: "not-allowed",
|
||||
transactionID: "transaction-1",
|
||||
},
|
||||
server: nokServer,
|
||||
expErr: errors.New("webhook server did not allow request"),
|
||||
},
|
||||
{
|
||||
name: "ok",
|
||||
fields: fields{http.DefaultClient, []*Webhook{
|
||||
{
|
||||
ID: "webhook-id-1",
|
||||
Name: "webhook-name-1",
|
||||
Secret: "MTIzNAo=",
|
||||
Kind: linkedca.Webhook_SCEPCHALLENGE.String(),
|
||||
CertType: linkedca.Webhook_X509.String(),
|
||||
URL: okServer.URL,
|
||||
},
|
||||
}},
|
||||
args: args{
|
||||
challenge: "challenge",
|
||||
transactionID: "transaction-1",
|
||||
},
|
||||
server: okServer,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := newChallengeValidationController(tt.fields.client, tt.fields.webhooks)
|
||||
|
||||
if tt.server != nil {
|
||||
defer tt.server.Close()
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
err := c.Validate(ctx, tt.args.challenge, tt.args.transactionID)
|
||||
|
||||
if tt.expErr != nil {
|
||||
assert.EqualError(t, err, tt.expErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestController_isCertTypeOK(t *testing.T) {
|
||||
assert.True(t, isCertTypeOK(&Webhook{CertType: linkedca.Webhook_X509.String()}))
|
||||
assert.True(t, isCertTypeOK(&Webhook{CertType: linkedca.Webhook_ALL.String()}))
|
||||
assert.True(t, isCertTypeOK(&Webhook{CertType: ""}))
|
||||
assert.False(t, isCertTypeOK(&Webhook{CertType: linkedca.Webhook_SSH.String()}))
|
||||
}
|
||||
|
||||
func Test_selectValidationMethod(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
p *SCEP
|
||||
want validationMethod
|
||||
}{
|
||||
{"webhooks", &SCEP{
|
||||
Name: "SCEP",
|
||||
Type: "SCEP",
|
||||
Options: &Options{
|
||||
Webhooks: []*Webhook{
|
||||
{
|
||||
Kind: linkedca.Webhook_SCEPCHALLENGE.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}, "webhook"},
|
||||
{"challenge", &SCEP{
|
||||
Name: "SCEP",
|
||||
Type: "SCEP",
|
||||
ChallengePassword: "pass",
|
||||
}, "static"},
|
||||
{"challenge-with-different-webhook", &SCEP{
|
||||
Name: "SCEP",
|
||||
Type: "SCEP",
|
||||
Options: &Options{
|
||||
Webhooks: []*Webhook{
|
||||
{
|
||||
Kind: linkedca.Webhook_AUTHORIZING.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
ChallengePassword: "pass",
|
||||
}, "static"},
|
||||
{"none", &SCEP{
|
||||
Name: "SCEP",
|
||||
Type: "SCEP",
|
||||
}, "none"},
|
||||
{"none-with-different-webhook", &SCEP{
|
||||
Name: "SCEP",
|
||||
Type: "SCEP",
|
||||
Options: &Options{
|
||||
Webhooks: []*Webhook{
|
||||
{
|
||||
Kind: linkedca.Webhook_AUTHORIZING.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}, "none"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.p.Init(Config{Claims: globalProvisionerClaims})
|
||||
require.NoError(t, err)
|
||||
got := tt.p.selectValidationMethod()
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSCEP_ValidateChallenge(t *testing.T) {
|
||||
type request struct {
|
||||
Challenge string `json:"scepChallenge"`
|
||||
TransactionID string `json:"scepTransactionID"`
|
||||
}
|
||||
type response struct {
|
||||
Allow bool `json:"allow"`
|
||||
}
|
||||
okServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
req := &request{}
|
||||
err := json.NewDecoder(r.Body).Decode(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "webhook-challenge", req.Challenge)
|
||||
assert.Equal(t, "webhook-transaction-1", req.TransactionID)
|
||||
b, err := json.Marshal(response{Allow: true})
|
||||
require.NoError(t, err)
|
||||
w.WriteHeader(200)
|
||||
w.Write(b)
|
||||
}))
|
||||
type args struct {
|
||||
challenge string
|
||||
transactionID string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
p *SCEP
|
||||
server *httptest.Server
|
||||
args args
|
||||
expErr error
|
||||
}{
|
||||
{"ok/webhooks", &SCEP{
|
||||
Name: "SCEP",
|
||||
Type: "SCEP",
|
||||
Options: &Options{
|
||||
Webhooks: []*Webhook{
|
||||
{
|
||||
ID: "webhook-id-1",
|
||||
Name: "webhook-name-1",
|
||||
Secret: "MTIzNAo=",
|
||||
Kind: linkedca.Webhook_SCEPCHALLENGE.String(),
|
||||
CertType: linkedca.Webhook_X509.String(),
|
||||
URL: okServer.URL,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, okServer, args{"webhook-challenge", "webhook-transaction-1"},
|
||||
nil,
|
||||
},
|
||||
{"fail/webhooks-secret-configuration", &SCEP{
|
||||
Name: "SCEP",
|
||||
Type: "SCEP",
|
||||
Options: &Options{
|
||||
Webhooks: []*Webhook{
|
||||
{
|
||||
ID: "webhook-id-1",
|
||||
Name: "webhook-name-1",
|
||||
Secret: "{{}}",
|
||||
Kind: linkedca.Webhook_SCEPCHALLENGE.String(),
|
||||
CertType: linkedca.Webhook_X509.String(),
|
||||
URL: okServer.URL,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil, args{"webhook-challenge", "webhook-transaction-1"},
|
||||
errors.New("failed executing webhook request: illegal base64 data at input byte 0"),
|
||||
},
|
||||
{"ok/static-challenge", &SCEP{
|
||||
Name: "SCEP",
|
||||
Type: "SCEP",
|
||||
Options: &Options{},
|
||||
ChallengePassword: "secret-static-challenge",
|
||||
}, nil, args{"secret-static-challenge", "static-transaction-1"},
|
||||
nil,
|
||||
},
|
||||
{"fail/wrong-static-challenge", &SCEP{
|
||||
Name: "SCEP",
|
||||
Type: "SCEP",
|
||||
Options: &Options{},
|
||||
ChallengePassword: "secret-static-challenge",
|
||||
}, nil, args{"the-wrong-challenge-secret", "static-transaction-1"},
|
||||
errors.New("invalid challenge password provided"),
|
||||
},
|
||||
{"ok/no-challenge", &SCEP{
|
||||
Name: "SCEP",
|
||||
Type: "SCEP",
|
||||
Options: &Options{},
|
||||
ChallengePassword: "",
|
||||
}, nil, args{"", "static-transaction-1"},
|
||||
nil,
|
||||
},
|
||||
{"fail/no-challenge-but-provided", &SCEP{
|
||||
Name: "SCEP",
|
||||
Type: "SCEP",
|
||||
Options: &Options{},
|
||||
ChallengePassword: "",
|
||||
}, nil, args{"a-challenge-value", "static-transaction-1"},
|
||||
errors.New("invalid challenge password provided"),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
if tt.server != nil {
|
||||
defer tt.server.Close()
|
||||
}
|
||||
|
||||
err := tt.p.Init(Config{Claims: globalProvisionerClaims, WebhookClient: http.DefaultClient})
|
||||
require.NoError(t, err)
|
||||
ctx := context.Background()
|
||||
|
||||
err = tt.p.ValidateChallenge(ctx, tt.args.challenge, tt.args.transactionID)
|
||||
if tt.expErr != nil {
|
||||
assert.EqualError(t, err, tt.expErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
@ -1,248 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/sha1" //nolint:gosec // used to create the Subject Key Identifier by RFC 5280
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"go.step.sm/cli-utils/fileutil"
|
||||
"go.step.sm/cli-utils/ui"
|
||||
"go.step.sm/crypto/kms/apiv1"
|
||||
"go.step.sm/crypto/kms/awskms"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var credentialsFile, region string
|
||||
var enableSSH bool
|
||||
flag.StringVar(&credentialsFile, "credentials-file", "", "Path to the `file` containing the AWS KMS credentials.")
|
||||
flag.StringVar(®ion, "region", "", "AWS KMS region name.")
|
||||
flag.BoolVar(&enableSSH, "ssh", false, "Create SSH keys.")
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
// Initialize windows terminal
|
||||
ui.Init()
|
||||
|
||||
ui.Println("⚠️ This command is deprecated and will be removed in future releases.")
|
||||
ui.Println("⚠️ Please use https://github.com/smallstep/step-kms-plugin instead.")
|
||||
|
||||
c, err := awskms.New(context.Background(), apiv1.Options{
|
||||
Type: apiv1.AmazonKMS,
|
||||
Region: region,
|
||||
CredentialsFile: credentialsFile,
|
||||
})
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
if err := createX509(c); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
if enableSSH {
|
||||
ui.Println()
|
||||
if err := createSSH(c); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Reset windows terminal
|
||||
ui.Reset()
|
||||
}
|
||||
|
||||
func fatal(err error) {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
ui.Reset()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintln(os.Stderr, "Usage: step-awskms-init")
|
||||
fmt.Fprintln(os.Stderr, `
|
||||
The step-awskms-init command initializes a public key infrastructure (PKI)
|
||||
to be used by step-ca.
|
||||
|
||||
This tool is experimental and in the future it will be integrated in step cli.
|
||||
|
||||
OPTIONS`)
|
||||
fmt.Fprintln(os.Stderr)
|
||||
flag.PrintDefaults()
|
||||
fmt.Fprintf(os.Stderr, `
|
||||
COPYRIGHT
|
||||
|
||||
(c) 2018-%d Smallstep Labs, Inc.
|
||||
`, time.Now().Year())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func createX509(c *awskms.KMS) error {
|
||||
ui.Println("Creating X.509 PKI ...")
|
||||
|
||||
// Root Certificate
|
||||
resp, err := c.CreateKey(&apiv1.CreateKeyRequest{
|
||||
Name: "root",
|
||||
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signer, err := c.CreateSigner(&resp.CreateSignerRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
root := &x509.Certificate{
|
||||
IsCA: true,
|
||||
NotBefore: now,
|
||||
NotAfter: now.Add(time.Hour * 24 * 365 * 10),
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||
BasicConstraintsValid: true,
|
||||
MaxPathLen: 1,
|
||||
MaxPathLenZero: false,
|
||||
Issuer: pkix.Name{CommonName: "Smallstep Root"},
|
||||
Subject: pkix.Name{CommonName: "Smallstep Root"},
|
||||
SerialNumber: mustSerialNumber(),
|
||||
SubjectKeyId: mustSubjectKeyID(resp.PublicKey),
|
||||
AuthorityKeyId: mustSubjectKeyID(resp.PublicKey),
|
||||
}
|
||||
|
||||
b, err := x509.CreateCertificate(rand.Reader, root, root, resp.PublicKey, signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fileutil.WriteFile("root_ca.crt", pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: b,
|
||||
}), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ui.PrintSelected("Root Key", resp.Name)
|
||||
ui.PrintSelected("Root Certificate", "root_ca.crt")
|
||||
|
||||
root, err = pemutil.ReadCertificate("root_ca.crt")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Intermediate Certificate
|
||||
resp, err = c.CreateKey(&apiv1.CreateKeyRequest{
|
||||
Name: "intermediate",
|
||||
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
intermediate := &x509.Certificate{
|
||||
IsCA: true,
|
||||
NotBefore: now,
|
||||
NotAfter: now.Add(time.Hour * 24 * 365 * 10),
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||
BasicConstraintsValid: true,
|
||||
MaxPathLen: 0,
|
||||
MaxPathLenZero: true,
|
||||
Issuer: root.Subject,
|
||||
Subject: pkix.Name{CommonName: "Smallstep Intermediate"},
|
||||
SerialNumber: mustSerialNumber(),
|
||||
SubjectKeyId: mustSubjectKeyID(resp.PublicKey),
|
||||
}
|
||||
|
||||
b, err = x509.CreateCertificate(rand.Reader, intermediate, root, resp.PublicKey, signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fileutil.WriteFile("intermediate_ca.crt", pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: b,
|
||||
}), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ui.PrintSelected("Intermediate Key", resp.Name)
|
||||
ui.PrintSelected("Intermediate Certificate", "intermediate_ca.crt")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createSSH(c *awskms.KMS) error {
|
||||
ui.Println("Creating SSH Keys ...")
|
||||
|
||||
// User Key
|
||||
resp, err := c.CreateKey(&apiv1.CreateKeyRequest{
|
||||
Name: "ssh-user-key",
|
||||
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key, err := ssh.NewPublicKey(resp.PublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fileutil.WriteFile("ssh_user_ca_key.pub", ssh.MarshalAuthorizedKey(key), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ui.PrintSelected("SSH User Public Key", "ssh_user_ca_key.pub")
|
||||
ui.PrintSelected("SSH User Private Key", resp.Name)
|
||||
|
||||
// Host Key
|
||||
resp, err = c.CreateKey(&apiv1.CreateKeyRequest{
|
||||
Name: "ssh-host-key",
|
||||
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key, err = ssh.NewPublicKey(resp.PublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fileutil.WriteFile("ssh_host_ca_key.pub", ssh.MarshalAuthorizedKey(key), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ui.PrintSelected("SSH Host Public Key", "ssh_host_ca_key.pub")
|
||||
ui.PrintSelected("SSH Host Private Key", resp.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func mustSerialNumber() *big.Int {
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
sn, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sn
|
||||
}
|
||||
|
||||
func mustSubjectKeyID(key crypto.PublicKey) []byte {
|
||||
b, err := x509.MarshalPKIXPublicKey(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//nolint:gosec // used to create the Subject Key Identifier by RFC 5280
|
||||
hash := sha1.Sum(b)
|
||||
return hash[:]
|
||||
}
|
@ -1,286 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/sha1" //nolint:gosec // used to create the Subject Key Identifier by RFC 5280
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.step.sm/cli-utils/fileutil"
|
||||
"go.step.sm/cli-utils/ui"
|
||||
"go.step.sm/crypto/kms/apiv1"
|
||||
"go.step.sm/crypto/kms/cloudkms"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var credentialsFile string
|
||||
var project, location, ring string
|
||||
var protectionLevelName string
|
||||
var enableSSH bool
|
||||
flag.StringVar(&credentialsFile, "credentials-file", "", "Path to the `file` containing the Google's Cloud KMS credentials.")
|
||||
flag.StringVar(&project, "project", "", "Google Cloud Project ID.")
|
||||
flag.StringVar(&location, "location", "global", "Cloud KMS location name.")
|
||||
flag.StringVar(&ring, "ring", "pki", "Cloud KMS ring name.")
|
||||
flag.StringVar(&protectionLevelName, "protection-level", "SOFTWARE", "Protection level to use, SOFTWARE or HSM.")
|
||||
flag.BoolVar(&enableSSH, "ssh", false, "Create SSH keys.")
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
switch {
|
||||
case project == "":
|
||||
usage()
|
||||
case location == "":
|
||||
fmt.Fprintln(os.Stderr, "flag `--location` is required")
|
||||
os.Exit(1)
|
||||
case ring == "":
|
||||
fmt.Fprintln(os.Stderr, "flag `--ring` is required")
|
||||
os.Exit(1)
|
||||
case protectionLevelName == "":
|
||||
fmt.Fprintln(os.Stderr, "flag `--protection-level` is required")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var protectionLevel apiv1.ProtectionLevel
|
||||
switch strings.ToUpper(protectionLevelName) {
|
||||
case "SOFTWARE":
|
||||
protectionLevel = apiv1.Software
|
||||
case "HSM":
|
||||
protectionLevel = apiv1.HSM
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "invalid value `%s` for flag `--protection-level`; options are `SOFTWARE` or `HSM`\n", protectionLevelName)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Initialize windows terminal
|
||||
ui.Init()
|
||||
|
||||
ui.Println("⚠️ This command is deprecated and will be removed in future releases.")
|
||||
ui.Println("⚠️ Please use https://github.com/smallstep/step-kms-plugin instead.")
|
||||
|
||||
c, err := cloudkms.New(context.Background(), apiv1.Options{
|
||||
Type: apiv1.CloudKMS,
|
||||
CredentialsFile: credentialsFile,
|
||||
})
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
if err := createPKI(c, project, location, ring, protectionLevel); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
if enableSSH {
|
||||
ui.Println()
|
||||
if err := createSSH(c, project, location, ring, protectionLevel); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Reset windows terminal
|
||||
ui.Reset()
|
||||
}
|
||||
|
||||
func fatal(err error) {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
ui.Reset()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintln(os.Stderr, "Usage: step-cloudkms-init --project <name>")
|
||||
fmt.Fprintln(os.Stderr, `
|
||||
The step-cloudkms-init command initializes a public key infrastructure (PKI)
|
||||
to be used by step-ca.
|
||||
|
||||
This tool is experimental and in the future it will be integrated in step cli.
|
||||
|
||||
OPTIONS`)
|
||||
fmt.Fprintln(os.Stderr)
|
||||
flag.PrintDefaults()
|
||||
fmt.Fprintf(os.Stderr, `
|
||||
COPYRIGHT
|
||||
|
||||
(c) 2018-%d Smallstep Labs, Inc.
|
||||
`, time.Now().Year())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func createPKI(c *cloudkms.CloudKMS, project, location, keyRing string, protectionLevel apiv1.ProtectionLevel) error {
|
||||
ui.Println("Creating PKI ...")
|
||||
|
||||
parent := "projects/" + project + "/locations/" + location + "/keyRings/" + keyRing + "/cryptoKeys"
|
||||
|
||||
// Root Certificate
|
||||
resp, err := c.CreateKey(&apiv1.CreateKeyRequest{
|
||||
Name: parent + "/root",
|
||||
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||
ProtectionLevel: protectionLevel,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signer, err := c.CreateSigner(&resp.CreateSignerRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
root := &x509.Certificate{
|
||||
IsCA: true,
|
||||
NotBefore: now,
|
||||
NotAfter: now.Add(time.Hour * 24 * 365 * 10),
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||
BasicConstraintsValid: true,
|
||||
MaxPathLen: 1,
|
||||
MaxPathLenZero: false,
|
||||
Issuer: pkix.Name{CommonName: "Smallstep Root"},
|
||||
Subject: pkix.Name{CommonName: "Smallstep Root"},
|
||||
SerialNumber: mustSerialNumber(),
|
||||
SubjectKeyId: mustSubjectKeyID(resp.PublicKey),
|
||||
AuthorityKeyId: mustSubjectKeyID(resp.PublicKey),
|
||||
}
|
||||
|
||||
b, err := x509.CreateCertificate(rand.Reader, root, root, resp.PublicKey, signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fileutil.WriteFile("root_ca.crt", pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: b,
|
||||
}), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ui.PrintSelected("Root Key", resp.Name)
|
||||
ui.PrintSelected("Root Certificate", "root_ca.crt")
|
||||
|
||||
root, err = pemutil.ReadCertificate("root_ca.crt")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Intermediate Certificate
|
||||
resp, err = c.CreateKey(&apiv1.CreateKeyRequest{
|
||||
Name: parent + "/intermediate",
|
||||
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||
ProtectionLevel: protectionLevel,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
intermediate := &x509.Certificate{
|
||||
IsCA: true,
|
||||
NotBefore: now,
|
||||
NotAfter: now.Add(time.Hour * 24 * 365 * 10),
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||
BasicConstraintsValid: true,
|
||||
MaxPathLen: 0,
|
||||
MaxPathLenZero: true,
|
||||
Issuer: root.Subject,
|
||||
Subject: pkix.Name{CommonName: "Smallstep Intermediate"},
|
||||
SerialNumber: mustSerialNumber(),
|
||||
SubjectKeyId: mustSubjectKeyID(resp.PublicKey),
|
||||
}
|
||||
|
||||
b, err = x509.CreateCertificate(rand.Reader, intermediate, root, resp.PublicKey, signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fileutil.WriteFile("intermediate_ca.crt", pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: b,
|
||||
}), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ui.PrintSelected("Intermediate Key", resp.Name)
|
||||
ui.PrintSelected("Intermediate Certificate", "intermediate_ca.crt")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createSSH(c *cloudkms.CloudKMS, project, location, keyRing string, protectionLevel apiv1.ProtectionLevel) error {
|
||||
ui.Println("Creating SSH Keys ...")
|
||||
|
||||
parent := "projects/" + project + "/locations/" + location + "/keyRings/" + keyRing + "/cryptoKeys"
|
||||
|
||||
// User Key
|
||||
resp, err := c.CreateKey(&apiv1.CreateKeyRequest{
|
||||
Name: parent + "/ssh-user-key",
|
||||
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||
ProtectionLevel: protectionLevel,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key, err := ssh.NewPublicKey(resp.PublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fileutil.WriteFile("ssh_user_ca_key.pub", ssh.MarshalAuthorizedKey(key), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ui.PrintSelected("SSH User Public Key", "ssh_user_ca_key.pub")
|
||||
ui.PrintSelected("SSH User Private Key", resp.Name)
|
||||
|
||||
// Host Key
|
||||
resp, err = c.CreateKey(&apiv1.CreateKeyRequest{
|
||||
Name: parent + "/ssh-host-key",
|
||||
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||
ProtectionLevel: protectionLevel,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key, err = ssh.NewPublicKey(resp.PublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fileutil.WriteFile("ssh_host_ca_key.pub", ssh.MarshalAuthorizedKey(key), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ui.PrintSelected("SSH Host Public Key", "ssh_host_ca_key.pub")
|
||||
ui.PrintSelected("SSH Host Private Key", resp.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func mustSerialNumber() *big.Int {
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
sn, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sn
|
||||
}
|
||||
|
||||
func mustSubjectKeyID(key crypto.PublicKey) []byte {
|
||||
b, err := x509.MarshalPKIXPublicKey(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//nolint:gosec // used to create the Subject Key Identifier by RFC 5280
|
||||
hash := sha1.Sum(b)
|
||||
return hash[:]
|
||||
}
|
@ -1,553 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha1" //nolint:gosec // used to create the Subject Key Identifier by RFC 5280
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"go.step.sm/cli-utils/fileutil"
|
||||
"go.step.sm/cli-utils/ui"
|
||||
"go.step.sm/crypto/kms"
|
||||
"go.step.sm/crypto/kms/apiv1"
|
||||
"go.step.sm/crypto/kms/uri"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
|
||||
// Enable pkcs11.
|
||||
_ "go.step.sm/crypto/kms/pkcs11"
|
||||
)
|
||||
|
||||
// Config is a mapping of the cli flags.
|
||||
type Config struct {
|
||||
KMS string
|
||||
GenerateRoot bool
|
||||
RootObject string
|
||||
RootKeyObject string
|
||||
RootSubject string
|
||||
RootPath string
|
||||
CrtObject string
|
||||
CrtPath string
|
||||
CrtKeyObject string
|
||||
CrtSubject string
|
||||
CrtKeyPath string
|
||||
SSHHostKeyObject string
|
||||
SSHUserKeyObject string
|
||||
RootFile string
|
||||
KeyFile string
|
||||
Pin string
|
||||
PinFile string
|
||||
NoCerts bool
|
||||
EnableSSH bool
|
||||
Force bool
|
||||
Extractable bool
|
||||
}
|
||||
|
||||
// Validate checks the flags in the config.
|
||||
func (c *Config) Validate() error {
|
||||
switch {
|
||||
case c.KMS == "":
|
||||
return errors.New("flag `--kms` is required")
|
||||
case c.CrtPath == "":
|
||||
return errors.New("flag `--crt-cert-path` is required")
|
||||
case c.RootFile != "" && c.KeyFile == "":
|
||||
return errors.New("flag `--root-cert-file` requires flag `--root-key-file`")
|
||||
case c.KeyFile != "" && c.RootFile == "":
|
||||
return errors.New("flag `--root-key-file` requires flag `--root-cert-file`")
|
||||
case c.RootFile == "" && c.RootObject == "":
|
||||
return errors.New("one of flag `--root-cert-file` or `--root-cert-obj` is required")
|
||||
case c.KeyFile == "" && c.RootKeyObject == "":
|
||||
return errors.New("one of flag `--root-key-file` or `--root-key-obj` is required")
|
||||
case c.CrtKeyPath == "" && c.CrtKeyObject == "":
|
||||
return errors.New("one of flag `--crt-key-path` or `--crt-key-obj` is required")
|
||||
case c.RootFile == "" && c.GenerateRoot && c.RootKeyObject == "":
|
||||
return errors.New("flag `--root-gen` requires flag `--root-key-obj`")
|
||||
case c.RootFile == "" && c.GenerateRoot && c.RootPath == "":
|
||||
return errors.New("flag `--root-gen` requires `--root-cert-path`")
|
||||
case c.Pin != "" && c.PinFile != "":
|
||||
return errors.New("Only set one of pin and pin-file")
|
||||
default:
|
||||
if c.RootFile != "" {
|
||||
c.GenerateRoot = false
|
||||
c.RootObject = ""
|
||||
c.RootKeyObject = ""
|
||||
}
|
||||
if c.CrtKeyPath != "" {
|
||||
c.CrtObject = ""
|
||||
c.CrtKeyObject = ""
|
||||
}
|
||||
if !c.EnableSSH {
|
||||
c.SSHHostKeyObject = ""
|
||||
c.SSHUserKeyObject = ""
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var kmsuri string
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
kmsuri = "pkcs11:module-path=/usr/local/lib/pkcs11/yubihsm_pkcs11.dylib;token=YubiHSM"
|
||||
case "linux":
|
||||
kmsuri = "pkcs11:module-path=/usr/lib/x86_64-linux-gnu/pkcs11/yubihsm_pkcs11.so;token=YubiHSM"
|
||||
case "windows":
|
||||
if home, err := os.UserHomeDir(); err == nil {
|
||||
kmsuri = "pkcs11:module-path=" + home + "\\yubihsm2-sdk\\bin\\yubihsm_pkcs11.dll" + ";token=YubiHSM"
|
||||
}
|
||||
}
|
||||
|
||||
var c Config
|
||||
flag.StringVar(&c.KMS, "kms", kmsuri, "PKCS #11 URI with the module-path and token to connect to the module.")
|
||||
flag.StringVar(&c.Pin, "pin", "", "PKCS #11 PIN")
|
||||
flag.StringVar(&c.PinFile, "pin-file", "", "PKCS #11 PIN File")
|
||||
// Option 1: Generate new root
|
||||
flag.BoolVar(&c.GenerateRoot, "root-gen", true, "Enable the generation of a root key.")
|
||||
flag.StringVar(&c.RootSubject, "root-name", "PKCS #11 Smallstep Root", "Subject and Issuer of the root certificate.")
|
||||
flag.StringVar(&c.RootObject, "root-cert-obj", "pkcs11:id=7330;object=root-cert", "PKCS #11 URI with object id and label to store the root certificate.")
|
||||
flag.StringVar(&c.RootKeyObject, "root-key-obj", "pkcs11:id=7330;object=root-key", "PKCS #11 URI with object id and label to store the root key.")
|
||||
// Option 2: Read root from disk and sign intermediate
|
||||
flag.StringVar(&c.RootFile, "root-cert-file", "", "Path to the root certificate to use.")
|
||||
flag.StringVar(&c.KeyFile, "root-key-file", "", "Path to the root key to use.")
|
||||
// Option 3: Generate certificate signing request
|
||||
flag.StringVar(&c.CrtSubject, "crt-name", "PKCS #11 Smallstep Intermediate", "Subject of the intermediate certificate.")
|
||||
flag.StringVar(&c.CrtObject, "crt-cert-obj", "pkcs11:id=7331;object=intermediate-cert", "PKCS #11 URI with object id and label to store the intermediate certificate.")
|
||||
flag.StringVar(&c.CrtKeyObject, "crt-key-obj", "pkcs11:id=7331;object=intermediate-key", "PKCS #11 URI with object id and label to store the intermediate certificate.")
|
||||
// SSH certificates
|
||||
flag.BoolVar(&c.EnableSSH, "ssh", false, "Enable the creation of ssh keys.")
|
||||
flag.StringVar(&c.SSHHostKeyObject, "ssh-host-key", "pkcs11:id=7332;object=ssh-host-key", "PKCS #11 URI with object id and label to store the key used to sign SSH host certificates.")
|
||||
flag.StringVar(&c.SSHUserKeyObject, "ssh-user-key", "pkcs11:id=7333;object=ssh-user-key", "PKCS #11 URI with object id and label to store the key used to sign SSH user certificates.")
|
||||
// Output files
|
||||
flag.StringVar(&c.RootPath, "root-cert-path", "root_ca.crt", "Location to write the root certificate.")
|
||||
flag.StringVar(&c.CrtPath, "crt-cert-path", "intermediate_ca.crt", "Location to write the intermediate certificate.")
|
||||
flag.StringVar(&c.CrtKeyPath, "crt-key-path", "", "Location to write the intermediate private key.")
|
||||
// Others
|
||||
flag.BoolVar(&c.NoCerts, "no-certs", false, "Do not store certificates in the module.")
|
||||
flag.BoolVar(&c.Force, "force", false, "Force the delete of previous keys.")
|
||||
flag.BoolVar(&c.Extractable, "extractable", false, "Allow export of private keys under wrap.")
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
if err := c.Validate(); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
u, err := uri.ParseWithScheme("pkcs11", c.KMS)
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
// Initialize windows terminal
|
||||
ui.Init()
|
||||
|
||||
ui.Println("⚠️ This command is deprecated and will be removed in future releases.")
|
||||
ui.Println("⚠️ Please use https://github.com/smallstep/step-kms-plugin instead.")
|
||||
|
||||
switch {
|
||||
case u.Get("pin-value") != "":
|
||||
case u.Get("pin-source") != "":
|
||||
case c.Pin != "":
|
||||
case c.PinFile != "":
|
||||
content, err := os.ReadFile(c.PinFile)
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
c.Pin = string(content)
|
||||
|
||||
default:
|
||||
pin, err := ui.PromptPassword("What is the PKCS#11 PIN?")
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
c.Pin = string(pin)
|
||||
}
|
||||
|
||||
k, err := kms.New(context.Background(), apiv1.Options{
|
||||
Type: apiv1.PKCS11,
|
||||
URI: c.KMS,
|
||||
Pin: c.Pin,
|
||||
})
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = k.Close()
|
||||
}()
|
||||
|
||||
// Check if the slots are empty, fail if they are not
|
||||
certUris := []string{
|
||||
c.RootObject, c.CrtObject,
|
||||
}
|
||||
keyUris := []string{
|
||||
c.RootKeyObject, c.CrtKeyObject,
|
||||
c.SSHHostKeyObject, c.SSHUserKeyObject,
|
||||
}
|
||||
if !c.Force {
|
||||
for _, u := range certUris {
|
||||
if u != "" && !c.NoCerts {
|
||||
checkObject(k, u)
|
||||
checkCertificate(k, u)
|
||||
}
|
||||
}
|
||||
for _, u := range keyUris {
|
||||
if u != "" {
|
||||
checkObject(k, u)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
deleter, ok := k.(interface {
|
||||
DeleteKey(uri string) error
|
||||
DeleteCertificate(uri string) error
|
||||
})
|
||||
if ok {
|
||||
for _, u := range certUris {
|
||||
if u != "" && !c.NoCerts {
|
||||
// Some HSMs like Nitrokey will overwrite the key with the
|
||||
// certificate label.
|
||||
if err := deleter.DeleteKey(u); err != nil {
|
||||
fatalClose(err, k)
|
||||
}
|
||||
if err := deleter.DeleteCertificate(u); err != nil {
|
||||
fatalClose(err, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, u := range keyUris {
|
||||
if u != "" {
|
||||
if err := deleter.DeleteKey(u); err != nil {
|
||||
fatalClose(err, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := createPKI(k, c); err != nil {
|
||||
fatalClose(err, k)
|
||||
}
|
||||
|
||||
// Reset windows terminal
|
||||
ui.Reset()
|
||||
}
|
||||
|
||||
func fatal(err error) {
|
||||
if os.Getenv("STEPDEBUG") == "1" {
|
||||
fmt.Fprintf(os.Stderr, "%+v\n", err)
|
||||
} else {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
ui.Reset()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func fatalClose(err error, k kms.KeyManager) {
|
||||
_ = k.Close()
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintln(os.Stderr, "Usage: step-pkcs11-init")
|
||||
fmt.Fprintln(os.Stderr, `
|
||||
The step-pkcs11-init command initializes a public key infrastructure (PKI)
|
||||
to be used by step-ca.
|
||||
|
||||
This tool is experimental and in the future it will be integrated in step cli.
|
||||
|
||||
OPTIONS`)
|
||||
fmt.Fprintln(os.Stderr)
|
||||
flag.PrintDefaults()
|
||||
fmt.Fprintf(os.Stderr, `
|
||||
COPYRIGHT
|
||||
|
||||
(c) 2018-%d Smallstep Labs, Inc.
|
||||
`, time.Now().Year())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func checkCertificate(k kms.KeyManager, rawuri string) {
|
||||
if cm, ok := k.(kms.CertificateManager); ok {
|
||||
if _, err := cm.LoadCertificate(&apiv1.LoadCertificateRequest{
|
||||
Name: rawuri,
|
||||
}); err == nil {
|
||||
fmt.Fprintf(os.Stderr, "⚠️ Your PKCS #11 module already has a certificate on %s.\n", rawuri)
|
||||
fmt.Fprintln(os.Stderr, " If you want to delete it and start fresh, use `--force`.")
|
||||
_ = k.Close()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkObject(k kms.KeyManager, rawuri string) {
|
||||
if _, err := k.GetPublicKey(&apiv1.GetPublicKeyRequest{
|
||||
Name: rawuri,
|
||||
}); err == nil {
|
||||
fmt.Fprintf(os.Stderr, "⚠️ Your PKCS #11 module already has a key on %s.\n", rawuri)
|
||||
fmt.Fprintln(os.Stderr, " If you want to delete it and start fresh, use `--force`.")
|
||||
_ = k.Close()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func createPKI(k kms.KeyManager, c Config) error {
|
||||
var err error
|
||||
ui.Println("Creating PKI ...")
|
||||
now := time.Now()
|
||||
|
||||
// Root Certificate
|
||||
var signer crypto.Signer
|
||||
var root *x509.Certificate
|
||||
switch {
|
||||
case c.GenerateRoot:
|
||||
resp, err := k.CreateKey(&apiv1.CreateKeyRequest{
|
||||
Name: c.RootKeyObject,
|
||||
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||
Extractable: c.Extractable,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signer, err = k.CreateSigner(&resp.CreateSignerRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
template := &x509.Certificate{
|
||||
IsCA: true,
|
||||
NotBefore: now,
|
||||
NotAfter: now.Add(time.Hour * 24 * 365 * 10),
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||
BasicConstraintsValid: true,
|
||||
MaxPathLen: 1,
|
||||
MaxPathLenZero: false,
|
||||
Issuer: pkix.Name{CommonName: c.RootSubject},
|
||||
Subject: pkix.Name{CommonName: c.RootSubject},
|
||||
SerialNumber: mustSerialNumber(),
|
||||
SubjectKeyId: mustSubjectKeyID(resp.PublicKey),
|
||||
AuthorityKeyId: mustSubjectKeyID(resp.PublicKey),
|
||||
}
|
||||
|
||||
b, err := x509.CreateCertificate(rand.Reader, template, template, resp.PublicKey, signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
root, err = x509.ParseCertificate(b)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error parsing root certificate")
|
||||
}
|
||||
|
||||
if cm, ok := k.(kms.CertificateManager); ok && c.RootObject != "" && !c.NoCerts {
|
||||
if err := cm.StoreCertificate(&apiv1.StoreCertificateRequest{
|
||||
Name: c.RootObject,
|
||||
Certificate: root,
|
||||
Extractable: c.Extractable,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
c.RootObject = ""
|
||||
}
|
||||
|
||||
if err := fileutil.WriteFile(c.RootPath, pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: b,
|
||||
}), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ui.PrintSelected("Root Key", resp.Name)
|
||||
ui.PrintSelected("Root Certificate", c.RootPath)
|
||||
if c.RootObject != "" {
|
||||
ui.PrintSelected("Root Certificate Object", c.RootObject)
|
||||
}
|
||||
case c.RootFile != "" && c.KeyFile != "": // Read Root From File
|
||||
root, err = pemutil.ReadCertificate(c.RootFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key, err := pemutil.Read(c.KeyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ok bool
|
||||
if signer, ok = key.(crypto.Signer); !ok {
|
||||
return errors.Errorf("key type '%T' does not implement a signer", key)
|
||||
}
|
||||
}
|
||||
|
||||
// Intermediate Certificate
|
||||
var keyName string
|
||||
var publicKey crypto.PublicKey
|
||||
var intSigner crypto.Signer
|
||||
if c.CrtKeyPath != "" {
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating intermediate key")
|
||||
}
|
||||
|
||||
pass, err := ui.PromptPasswordGenerate("What do you want your password to be? [leave empty and we'll generate one]",
|
||||
ui.WithRichPrompt())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = pemutil.Serialize(priv, pemutil.WithPassword(pass), pemutil.ToFile(c.CrtKeyPath, 0600))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
publicKey = priv.Public()
|
||||
intSigner = priv
|
||||
} else {
|
||||
resp, err := k.CreateKey(&apiv1.CreateKeyRequest{
|
||||
Name: c.CrtKeyObject,
|
||||
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||
Extractable: c.Extractable,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
publicKey = resp.PublicKey
|
||||
keyName = resp.Name
|
||||
|
||||
intSigner, err = k.CreateSigner(&resp.CreateSignerRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if root != nil {
|
||||
template := &x509.Certificate{
|
||||
IsCA: true,
|
||||
NotBefore: now,
|
||||
NotAfter: now.Add(time.Hour * 24 * 365 * 10),
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||
BasicConstraintsValid: true,
|
||||
MaxPathLen: 0,
|
||||
MaxPathLenZero: true,
|
||||
Issuer: root.Subject,
|
||||
Subject: pkix.Name{CommonName: c.CrtSubject},
|
||||
SerialNumber: mustSerialNumber(),
|
||||
SubjectKeyId: mustSubjectKeyID(publicKey),
|
||||
}
|
||||
|
||||
b, err := x509.CreateCertificate(rand.Reader, template, root, publicKey, signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
intermediate, err := x509.ParseCertificate(b)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error parsing intermediate certificate")
|
||||
}
|
||||
|
||||
if cm, ok := k.(kms.CertificateManager); ok && c.CrtObject != "" && !c.NoCerts {
|
||||
if err := cm.StoreCertificate(&apiv1.StoreCertificateRequest{
|
||||
Name: c.CrtObject,
|
||||
Certificate: intermediate,
|
||||
Extractable: c.Extractable,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
c.CrtObject = ""
|
||||
}
|
||||
|
||||
if err := fileutil.WriteFile(c.CrtPath, pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: b,
|
||||
}), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// No root available, generate CSR for external root.
|
||||
csrTemplate := x509.CertificateRequest{
|
||||
Subject: pkix.Name{CommonName: c.CrtSubject},
|
||||
SignatureAlgorithm: x509.ECDSAWithSHA256,
|
||||
}
|
||||
// step: generate the csr request
|
||||
csrCertificate, err := x509.CreateCertificateRequest(rand.Reader, &csrTemplate, intSigner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fileutil.WriteFile(c.CrtPath, pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE REQUEST",
|
||||
Bytes: csrCertificate,
|
||||
}), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if c.CrtKeyPath != "" {
|
||||
ui.PrintSelected("Intermediate Key", c.CrtKeyPath)
|
||||
} else {
|
||||
ui.PrintSelected("Intermediate Key", keyName)
|
||||
}
|
||||
|
||||
if root != nil {
|
||||
ui.PrintSelected("Intermediate Certificate", c.CrtPath)
|
||||
if c.CrtObject != "" {
|
||||
ui.PrintSelected("Intermediate Certificate Object", c.CrtObject)
|
||||
}
|
||||
} else {
|
||||
ui.PrintSelected("Intermediate Certificate Request", c.CrtPath)
|
||||
}
|
||||
|
||||
if c.SSHHostKeyObject != "" {
|
||||
resp, err := k.CreateKey(&apiv1.CreateKeyRequest{
|
||||
Name: c.SSHHostKeyObject,
|
||||
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ui.PrintSelected("SSH Host Key", resp.Name)
|
||||
}
|
||||
|
||||
if c.SSHUserKeyObject != "" {
|
||||
resp, err := k.CreateKey(&apiv1.CreateKeyRequest{
|
||||
Name: c.SSHUserKeyObject,
|
||||
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ui.PrintSelected("SSH User Key", resp.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func mustSerialNumber() *big.Int {
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
sn, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sn
|
||||
}
|
||||
|
||||
func mustSubjectKeyID(key crypto.PublicKey) []byte {
|
||||
b, err := x509.MarshalPKIXPublicKey(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//nolint:gosec // used to create the Subject Key Identifier by RFC 5280
|
||||
hash := sha1.Sum(b)
|
||||
return hash[:]
|
||||
}
|
@ -1,355 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha1" //nolint:gosec // used to create the Subject Key Identifier by RFC 5280
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"go.step.sm/cli-utils/fileutil"
|
||||
"go.step.sm/cli-utils/ui"
|
||||
"go.step.sm/crypto/kms"
|
||||
"go.step.sm/crypto/kms/apiv1"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
|
||||
// Enable yubikey.
|
||||
_ "go.step.sm/crypto/kms/yubikey"
|
||||
)
|
||||
|
||||
// Config is a mapping of the cli flags.
|
||||
type Config struct {
|
||||
RootOnly bool
|
||||
RootSlot string
|
||||
CrtSlot string
|
||||
RootFile string
|
||||
KeyFile string
|
||||
Pin string
|
||||
ManagementKey string
|
||||
Force bool
|
||||
}
|
||||
|
||||
// Validate checks the flags in the config.
|
||||
func (c *Config) Validate() error {
|
||||
switch {
|
||||
case c.ManagementKey != "" && len(c.ManagementKey) != 48:
|
||||
return errors.New("flag `--management-key` must be 48 hexadecimal characters (24 bytes)")
|
||||
case c.RootFile != "" && c.KeyFile == "":
|
||||
return errors.New("flag `--root` requires flag `--key`")
|
||||
case c.KeyFile != "" && c.RootFile == "":
|
||||
return errors.New("flag `--key` requires flag `--root`")
|
||||
case c.RootOnly && c.RootFile != "":
|
||||
return errors.New("flag `--root-only` is incompatible with flag `--root`")
|
||||
case c.RootSlot == c.CrtSlot:
|
||||
return errors.New("flag `--root-slot` and flag `--crt-slot` cannot be the same")
|
||||
case c.RootFile == "" && c.RootSlot == "":
|
||||
return errors.New("one of flag `--root` or `--root-slot` is required")
|
||||
default:
|
||||
if c.RootFile != "" {
|
||||
c.RootSlot = ""
|
||||
}
|
||||
if c.RootOnly {
|
||||
c.CrtSlot = ""
|
||||
}
|
||||
if c.ManagementKey != "" {
|
||||
if _, err := hex.DecodeString(c.ManagementKey); err != nil {
|
||||
return errors.Wrap(err, "flag `--management-key` is not valid")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var c Config
|
||||
flag.StringVar(&c.ManagementKey, "management-key", "", `Management key to use in hexadecimal format. (default "010203040506070801020304050607080102030405060708")`)
|
||||
flag.BoolVar(&c.RootOnly, "root-only", false, "Slot only the root certificate and sign and intermediate.")
|
||||
flag.StringVar(&c.RootSlot, "root-slot", "9a", "Slot to store the root certificate.")
|
||||
flag.StringVar(&c.CrtSlot, "crt-slot", "9c", "Slot to store the intermediate certificate.")
|
||||
flag.StringVar(&c.RootFile, "root", "", "Path to the root certificate to use.")
|
||||
flag.StringVar(&c.KeyFile, "key", "", "Path to the root key to use.")
|
||||
flag.BoolVar(&c.Force, "force", false, "Force the delete of previous keys.")
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
if err := c.Validate(); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
// Initialize windows terminal
|
||||
ui.Init()
|
||||
|
||||
ui.Println("⚠️ This command is deprecated and will be removed in future releases.")
|
||||
ui.Println("⚠️ Please use https://github.com/smallstep/step-kms-plugin instead.")
|
||||
|
||||
pin, err := ui.PromptPassword("What is the YubiKey PIN?")
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
c.Pin = string(pin)
|
||||
|
||||
k, err := kms.New(context.Background(), apiv1.Options{
|
||||
Type: apiv1.YubiKey,
|
||||
Pin: c.Pin,
|
||||
ManagementKey: c.ManagementKey,
|
||||
})
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
// Check if the slots are empty, fail if they are not
|
||||
if !c.Force {
|
||||
switch {
|
||||
case c.RootSlot != "":
|
||||
checkSlot(k, c.RootSlot)
|
||||
case c.CrtSlot != "":
|
||||
checkSlot(k, c.CrtSlot)
|
||||
}
|
||||
}
|
||||
|
||||
if err := createPKI(k, c); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = k.Close()
|
||||
}()
|
||||
|
||||
// Reset windows terminal
|
||||
ui.Reset()
|
||||
}
|
||||
|
||||
func fatal(err error) {
|
||||
if os.Getenv("STEPDEBUG") == "1" {
|
||||
fmt.Fprintf(os.Stderr, "%+v\n", err)
|
||||
} else {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
ui.Reset()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintln(os.Stderr, "Usage: step-yubikey-init")
|
||||
fmt.Fprintln(os.Stderr, `
|
||||
The step-yubikey-init command initializes a public key infrastructure (PKI)
|
||||
to be used by step-ca.
|
||||
|
||||
This tool is experimental and in the future it will be integrated in step cli.
|
||||
|
||||
OPTIONS`)
|
||||
fmt.Fprintln(os.Stderr)
|
||||
flag.PrintDefaults()
|
||||
fmt.Fprintf(os.Stderr, `
|
||||
COPYRIGHT
|
||||
|
||||
(c) 2018-%d Smallstep Labs, Inc.
|
||||
`, time.Now().Year())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func checkSlot(k kms.KeyManager, slot string) {
|
||||
if _, err := k.GetPublicKey(&apiv1.GetPublicKeyRequest{
|
||||
Name: slot,
|
||||
}); err == nil {
|
||||
fmt.Fprintf(os.Stderr, "⚠️ Your YubiKey already has a key in the slot %s.\n", slot)
|
||||
fmt.Fprintln(os.Stderr, " If you want to delete it and start fresh, use `--force`.")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func createPKI(k kms.KeyManager, c Config) error {
|
||||
var err error
|
||||
ui.Println("Creating PKI ...")
|
||||
now := time.Now()
|
||||
|
||||
// Root Certificate
|
||||
var signer crypto.Signer
|
||||
var root *x509.Certificate
|
||||
if c.RootFile != "" && c.KeyFile != "" {
|
||||
root, err = pemutil.ReadCertificate(c.RootFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key, err := pemutil.Read(c.KeyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ok bool
|
||||
if signer, ok = key.(crypto.Signer); !ok {
|
||||
return errors.Errorf("key type '%T' does not implement a signer", key)
|
||||
}
|
||||
} else {
|
||||
resp, err := k.CreateKey(&apiv1.CreateKeyRequest{
|
||||
Name: c.RootSlot,
|
||||
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signer, err = k.CreateSigner(&resp.CreateSignerRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
template := &x509.Certificate{
|
||||
IsCA: true,
|
||||
NotBefore: now,
|
||||
NotAfter: now.Add(time.Hour * 24 * 365 * 10),
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||
BasicConstraintsValid: true,
|
||||
MaxPathLen: 1,
|
||||
MaxPathLenZero: false,
|
||||
Issuer: pkix.Name{CommonName: "YubiKey Smallstep Root"},
|
||||
Subject: pkix.Name{CommonName: "YubiKey Smallstep Root"},
|
||||
SerialNumber: mustSerialNumber(),
|
||||
SubjectKeyId: mustSubjectKeyID(resp.PublicKey),
|
||||
AuthorityKeyId: mustSubjectKeyID(resp.PublicKey),
|
||||
}
|
||||
|
||||
b, err := x509.CreateCertificate(rand.Reader, template, template, resp.PublicKey, signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
root, err = x509.ParseCertificate(b)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error parsing root certificate")
|
||||
}
|
||||
|
||||
if cm, ok := k.(kms.CertificateManager); ok {
|
||||
if err := cm.StoreCertificate(&apiv1.StoreCertificateRequest{
|
||||
Name: c.RootSlot,
|
||||
Certificate: root,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := fileutil.WriteFile("root_ca.crt", pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: b,
|
||||
}), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ui.PrintSelected("Root Key", resp.Name)
|
||||
ui.PrintSelected("Root Certificate", "root_ca.crt")
|
||||
}
|
||||
|
||||
// Intermediate Certificate
|
||||
var keyName string
|
||||
var publicKey crypto.PublicKey
|
||||
if c.RootOnly {
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating intermediate key")
|
||||
}
|
||||
|
||||
pass, err := ui.PromptPasswordGenerate("What do you want your password to be? [leave empty and we'll generate one]",
|
||||
ui.WithRichPrompt())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = pemutil.Serialize(priv, pemutil.WithPassword(pass), pemutil.ToFile("intermediate_ca_key", 0600))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
publicKey = priv.Public()
|
||||
} else {
|
||||
resp, err := k.CreateKey(&apiv1.CreateKeyRequest{
|
||||
Name: c.CrtSlot,
|
||||
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
publicKey = resp.PublicKey
|
||||
keyName = resp.Name
|
||||
}
|
||||
|
||||
template := &x509.Certificate{
|
||||
IsCA: true,
|
||||
NotBefore: now,
|
||||
NotAfter: now.Add(time.Hour * 24 * 365 * 10),
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||
BasicConstraintsValid: true,
|
||||
MaxPathLen: 0,
|
||||
MaxPathLenZero: true,
|
||||
Issuer: root.Subject,
|
||||
Subject: pkix.Name{CommonName: "YubiKey Smallstep Intermediate"},
|
||||
SerialNumber: mustSerialNumber(),
|
||||
SubjectKeyId: mustSubjectKeyID(publicKey),
|
||||
}
|
||||
|
||||
b, err := x509.CreateCertificate(rand.Reader, template, root, publicKey, signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
intermediate, err := x509.ParseCertificate(b)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error parsing intermediate certificate")
|
||||
}
|
||||
|
||||
if cm, ok := k.(kms.CertificateManager); ok {
|
||||
if err := cm.StoreCertificate(&apiv1.StoreCertificateRequest{
|
||||
Name: c.CrtSlot,
|
||||
Certificate: intermediate,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := fileutil.WriteFile("intermediate_ca.crt", pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: b,
|
||||
}), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.RootOnly {
|
||||
ui.PrintSelected("Intermediate Key", "intermediate_ca_key")
|
||||
} else {
|
||||
ui.PrintSelected("Intermediate Key", keyName)
|
||||
}
|
||||
|
||||
ui.PrintSelected("Intermediate Certificate", "intermediate_ca.crt")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func mustSerialNumber() *big.Int {
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
sn, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sn
|
||||
}
|
||||
|
||||
func mustSubjectKeyID(key crypto.PublicKey) []byte {
|
||||
b, err := x509.MarshalPKIXPublicKey(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//nolint:gosec // used to create the Subject Key Identifier by RFC 5280
|
||||
hash := sha1.Sum(b)
|
||||
return hash[:]
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
step-ca (0.8.4-14-ge72f087-dev) unstable; urgency=medium
|
||||
|
||||
* See https://github.com/smallstep/certificates/releases
|
||||
|
||||
-- Smallstep Labs, Inc. <techadmin@smallstep.com> Wed, 20 Feb 2019 20:44:25 +0000
|
@ -1 +0,0 @@
|
||||
10
|
@ -1,15 +0,0 @@
|
||||
Source: step-ca
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Maintainer: Smallstep Labs, Inc. <techadmin@smallstep.com>
|
||||
Build-Depends: debhelper (>= 9), git, bash-completion
|
||||
Standards-Version: 4.2.0
|
||||
Homepage: https://github.com/smallstep/certificates
|
||||
Vcs-Browser: https://github.com/smallstep/certificates.git
|
||||
Vcs-Git: https://github.com/smallstep/certificates.git
|
||||
|
||||
Package: step-ca
|
||||
Architecture: any
|
||||
Depends: ${misc:Depends}
|
||||
Description: Smallstep Certificate Authority
|
||||
step-ca is the Smallstep Certificate Authority.
|
@ -1,13 +0,0 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
override_dh_install-arch:
|
||||
dh_install --arch
|
||||
|
||||
build:
|
||||
dh build
|
||||
|
||||
override_dh_auto_build:
|
||||
dh_auto_build -- build
|
||||
|
||||
%:
|
||||
dh $@
|
@ -1 +0,0 @@
|
||||
3.0 (quilt)
|
@ -0,0 +1,36 @@
|
||||
FROM golang AS builder
|
||||
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y --no-install-recommends \
|
||||
gcc pkgconf libpcsclite-dev libcap2-bin
|
||||
RUN make V=1 GOFLAGS="" bin/step-ca
|
||||
RUN setcap CAP_NET_BIND_SERVICE=+eip bin/step-ca
|
||||
|
||||
FROM smallstep/step-kms-plugin:bullseye AS kms
|
||||
|
||||
FROM smallstep/step-cli:bullseye
|
||||
|
||||
COPY --from=builder /src/bin/step-ca /usr/local/bin/step-ca
|
||||
COPY --from=kms /usr/local/bin/step-kms-plugin /usr/local/bin/step-kms-plugin
|
||||
|
||||
USER root
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y --no-install-recommends pcscd libpcsclite1
|
||||
RUN mkdir -p /run/pcscd
|
||||
RUN chown step:step /run/pcscd
|
||||
USER step
|
||||
|
||||
ENV CONFIGPATH="/home/step/config/ca.json"
|
||||
ENV PWDPATH="/home/step/secrets/password"
|
||||
|
||||
VOLUME ["/home/step"]
|
||||
STOPSIGNAL SIGTERM
|
||||
HEALTHCHECK CMD step ca health 2>/dev/null | grep "^ok" >/dev/null
|
||||
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/bin/bash", "/entrypoint.sh"]
|
||||
CMD exec /usr/local/bin/step-ca --password-file $PWDPATH $CONFIGPATH
|
@ -1,35 +0,0 @@
|
||||
FROM golang:alpine AS builder
|
||||
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
|
||||
RUN apk add --no-cache curl git make
|
||||
RUN apk add --no-cache gcc musl-dev pkgconf pcsc-lite-dev
|
||||
RUN make V=1 download
|
||||
RUN make V=1 GOFLAGS="" build
|
||||
|
||||
|
||||
FROM smallstep/step-cli:latest
|
||||
|
||||
COPY --from=builder /src/bin/step-ca /usr/local/bin/step-ca
|
||||
COPY --from=builder /src/bin/step-awskms-init /usr/local/bin/step-awskms-init
|
||||
COPY --from=builder /src/bin/step-cloudkms-init /usr/local/bin/step-cloudkms-init
|
||||
COPY --from=builder /src/bin/step-pkcs11-init /usr/local/bin/step-pkcs11-init
|
||||
COPY --from=builder /src/bin/step-yubikey-init /usr/local/bin/step-yubikey-init
|
||||
|
||||
USER root
|
||||
RUN apk add --no-cache libcap && setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/step-ca
|
||||
RUN apk add --no-cache pcsc-lite pcsc-lite-libs
|
||||
USER step
|
||||
|
||||
ENV CONFIGPATH="/home/step/config/ca.json"
|
||||
ENV PWDPATH="/home/step/secrets/password"
|
||||
|
||||
VOLUME ["/home/step"]
|
||||
STOPSIGNAL SIGTERM
|
||||
HEALTHCHECK CMD step ca health 2>/dev/null | grep "^ok" >/dev/null
|
||||
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/bin/bash", "/entrypoint.sh"]
|
||||
CMD exec /usr/local/bin/step-ca --password-file $PWDPATH $CONFIGPATH
|
Loading…
Reference in New Issue