| // Copyright 2016 The LUCI Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package machinetoken |
| |
| import ( |
| "context" |
| "crypto/x509" |
| "crypto/x509/pkix" |
| "math/big" |
| "testing" |
| "time" |
| |
| "go.chromium.org/luci/common/clock/testclock" |
| "go.chromium.org/luci/server/auth/signing" |
| |
| tokenserver "go.chromium.org/luci/tokenserver/api" |
| "go.chromium.org/luci/tokenserver/api/admin/v1" |
| |
| . "github.com/smartystreets/goconvey/convey" |
| . "go.chromium.org/luci/common/testing/assertions" |
| ) |
| |
| func TestMachineFQDN(t *testing.T) { |
| // See rpc_mocks_test.go for getTestCert, certWithCN and certWithSAN. |
| |
| // Test parsing of the real certs. |
| |
| Convey("MachineFQDN works for cert without SAN", t, func() { |
| params := MintParams{Cert: getTestCert(certWithCN)} |
| fqdn, err := params.MachineFQDN() |
| So(err, ShouldBeNil) |
| So(fqdn, ShouldEqual, "luci-token-server-test-1.fake.domain") |
| }) |
| |
| Convey("MachineFQDN works for cert with SAN", t, func() { |
| params := MintParams{Cert: getTestCert(certWithSAN)} |
| fqdn, err := params.MachineFQDN() |
| So(err, ShouldBeNil) |
| So(fqdn, ShouldEqual, "fuchsia-debian-dev-141242e1-us-central1-f-0psd.c.fuchsia-infra.internal") |
| }) |
| |
| Convey("MachineFQDN works for cert where CN == SAN", t, func() { |
| params := MintParams{Cert: getTestCert(certWithCNEqualSAN)} |
| fqdn, err := params.MachineFQDN() |
| So(err, ShouldBeNil) |
| So(fqdn, ShouldEqual, "proto-chrome-focal.c.chromecompute.google.com.internal") |
| }) |
| |
| Convey("MachineFQDN with more than one SAN", t, func() { |
| params := MintParams{ |
| Cert: &x509.Certificate{ |
| Subject: pkix.Name{CommonName: "name1"}, |
| DNSNames: []string{"name1.example.com", "name2.example.com"}, |
| }, |
| } |
| fqdn, err := params.MachineFQDN() |
| So(fqdn, ShouldEqual, "name1.example.com") |
| So(err, ShouldBeNil) |
| }) |
| |
| // Test some synthetic cases. |
| |
| Convey("MachineFQDN with empty CN", t, func() { |
| params := MintParams{ |
| Cert: &x509.Certificate{ |
| DNSNames: []string{"name1.example.com"}, |
| }, |
| } |
| _, err := params.MachineFQDN() |
| So(err, ShouldErrLike, "unsupported cert, Subject CN field is required") |
| }) |
| } |
| |
| func TestMintParamsValidation(t *testing.T) { |
| Convey("with token params", t, func() { |
| params := MintParams{ |
| Cert: &x509.Certificate{ |
| Subject: pkix.Name{CommonName: "host.domain"}, |
| SerialNumber: big.NewInt(12345), |
| }, |
| Config: &admin.CertificateAuthorityConfig{ |
| KnownDomains: []*admin.DomainConfig{ |
| { |
| Domain: []string{"domain"}, |
| MachineTokenLifetime: 3600, |
| }, |
| }, |
| }, |
| } |
| |
| Convey("good params", func() { |
| So(params.Validate(), ShouldBeNil) |
| fqdn, err := params.MachineFQDN() |
| So(err, ShouldBeNil) |
| So(fqdn, ShouldEqual, "host.domain") |
| }) |
| |
| Convey("good params with subdomain", func() { |
| params.Cert.Subject.CommonName = "host.subdomain.domain" |
| So(params.Validate(), ShouldBeNil) |
| fqdn, err := params.MachineFQDN() |
| So(err, ShouldBeNil) |
| So(fqdn, ShouldEqual, "host.subdomain.domain") |
| }) |
| |
| Convey("bad FQDN case is converted to lowercase", func() { |
| params.Cert.Subject.CommonName = "HOST.domain" |
| So(params.Validate(), ShouldBeNil) |
| fqdn, err := params.MachineFQDN() |
| So(err, ShouldBeNil) |
| So(fqdn, ShouldEqual, "host.domain") |
| }) |
| |
| Convey("bad FQDN", func() { |
| params.Cert.Subject.CommonName = "host" |
| So(params.Validate(), ShouldErrLike, "not a valid FQDN") |
| }) |
| |
| Convey("not listed", func() { |
| params.Cert.Subject.CommonName = "host.blah" |
| So(params.Validate(), ShouldErrLike, "not listed in the config") |
| }) |
| |
| Convey("tokens are not allowed", func() { |
| params.Config.KnownDomains[0].MachineTokenLifetime = 0 |
| So(params.Validate(), ShouldErrLike, "are not allowed") |
| }) |
| |
| Convey("bad SN", func() { |
| params.Cert.SerialNumber = big.NewInt(-1) |
| So(params.Validate(), ShouldErrLike, "invalid certificate serial number") |
| }) |
| }) |
| } |
| |
| func TestMint(t *testing.T) { |
| Convey("with mock context", t, func() { |
| ctx := context.Background() |
| ctx, _ = testclock.UseTime(ctx, time.Date(2015, time.February, 3, 4, 5, 6, 7, time.UTC)) |
| |
| Convey("works", func() { |
| params := MintParams{ |
| Cert: &x509.Certificate{ |
| Subject: pkix.Name{CommonName: "host.domain"}, |
| SerialNumber: big.NewInt(12345), |
| }, |
| Config: &admin.CertificateAuthorityConfig{ |
| KnownDomains: []*admin.DomainConfig{ |
| { |
| Domain: []string{"domain"}, |
| MachineTokenLifetime: 3600, |
| }, |
| }, |
| }, |
| Signer: fakeSigner{}, |
| } |
| body, token, err := Mint(ctx, ¶ms) |
| So(err, ShouldBeNil) |
| So(body, ShouldResembleProto, &tokenserver.MachineTokenBody{ |
| MachineFqdn: "host.domain", |
| IssuedBy: "token-server@example.com", |
| IssuedAt: 1422936306, |
| Lifetime: 3600, |
| CaId: 0, |
| CertSn: big.NewInt(12345).Bytes(), |
| }) |
| So(token, ShouldEqual, "CjQKC2hvc3QuZG9tYWluEhh0b2tlbi1zZXJ2ZXJAZXhhbXB"+ |
| "sZS5jb20Y8pHBpgUgkBw6AjA5EgZrZXlfaWQaCXNpZ25hdHVyZQ") |
| }) |
| }) |
| } |
| |
| type fakeSigner struct{} |
| |
| func (fakeSigner) SignBytes(c context.Context, blob []byte) (keyID string, sig []byte, err error) { |
| return "key_id", []byte("signature"), nil |
| } |
| |
| func (fakeSigner) Certificates(c context.Context) (*signing.PublicCertificates, error) { |
| panic("not implemented yet") |
| } |
| |
| func (fakeSigner) ServiceInfo(c context.Context) (*signing.ServiceInfo, error) { |
| return &signing.ServiceInfo{ |
| ServiceAccountName: "token-server@example.com", |
| }, nil |
| } |