go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/tokenserver/appengine/impl/certchecker/certchecker_test.go (about) 1 // Copyright 2016 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package certchecker 16 17 import ( 18 "context" 19 "crypto/rsa" 20 "crypto/x509" 21 "crypto/x509/pkix" 22 "math/big" 23 "testing" 24 "time" 25 26 "google.golang.org/protobuf/proto" 27 28 "go.chromium.org/luci/appengine/gaetesting" 29 "go.chromium.org/luci/common/clock" 30 "go.chromium.org/luci/common/clock/testclock" 31 "go.chromium.org/luci/common/data/rand/cryptorand" 32 "go.chromium.org/luci/gae/service/datastore" 33 "go.chromium.org/luci/tokenserver/api/admin/v1" 34 "go.chromium.org/luci/tokenserver/appengine/impl/certconfig" 35 36 . "github.com/smartystreets/goconvey/convey" 37 . "go.chromium.org/luci/common/testing/assertions" 38 ) 39 40 func TestCertChecker(t *testing.T) { 41 Convey("CertChecker works", t, func() { 42 ctx := gaetesting.TestingContext() 43 ctx = cryptorand.MockForTest(ctx, 0) 44 ctx, clk := testclock.UseTime(ctx, testclock.TestTimeUTC) 45 46 // Generate new CA private key and certificate. 47 pkey, caCert, err := generateCA(ctx, "Some CA: ca-name.fake") 48 So(err, ShouldBeNil) 49 50 // Generate phony CA config. 51 configBlob, _ := proto.Marshal(&admin.CertificateAuthorityConfig{ 52 UniqueId: 0, 53 Cn: "Some CA: ca-name.fake", 54 CrlUrl: "http://example.com", 55 }) 56 57 // Nothing in the datastore yet. 58 checker, err := GetCertChecker(ctx, "Some CA: ca-name.fake") 59 So(err, ShouldNotBeNil) 60 61 // Put it into the datastore. 62 caEntity := certconfig.CA{ 63 CN: "Some CA: ca-name.fake", 64 Config: configBlob, 65 Cert: caCert, 66 Ready: true, 67 } 68 err = datastore.Put(ctx, &caEntity) 69 So(err, ShouldBeNil) 70 71 // In the datastore now. 72 checker, err = GetCertChecker(ctx, "Some CA: ca-name.fake") 73 So(err, ShouldBeNil) 74 75 // Update associated CRL (it's empty). 76 err = certconfig.UpdateCRLSet(ctx, "Some CA: ca-name.fake", 77 certconfig.CRLShardCount, &pkix.CertificateList{}) 78 So(err, ShouldBeNil) 79 80 // Generate some certificate signed by the CA. 81 certDer, err := generateCert(ctx, 2, "some-cert-name.fake", caCert, pkey) 82 So(err, ShouldBeNil) 83 84 // Use CertChecker to check its validity. Need to parse DER first. 85 parsedCert, err := x509.ParseCertificate(certDer) 86 So(err, ShouldBeNil) 87 So(parsedCert.Issuer.CommonName, ShouldEqual, "Some CA: ca-name.fake") 88 89 // Valid! 90 ca, err := checker.CheckCertificate(ctx, parsedCert) 91 So(err, ShouldBeNil) 92 So(ca.CN, ShouldEqual, "Some CA: ca-name.fake") 93 So(ca.ParsedConfig, ShouldNotBeNil) 94 95 // Revoke the certificate by generating new CRL and putting it into the 96 // datastore. 97 err = certconfig.UpdateCRLSet(ctx, "Some CA: ca-name.fake", certconfig.CRLShardCount, 98 &pkix.CertificateList{ 99 TBSCertList: pkix.TBSCertificateList{ 100 RevokedCertificates: []pkix.RevokedCertificate{ 101 {SerialNumber: big.NewInt(2)}, 102 }, 103 }, 104 }) 105 So(err, ShouldBeNil) 106 107 // Bump time to invalidate cert checker caches. 108 clk.Add(10 * time.Minute) 109 110 // Check same cert again. Should be rejected now as revoked. 111 _, err = checker.CheckCertificate(ctx, parsedCert) 112 So(err, ShouldErrLike, "certificate with SN 2 has been revoked") 113 114 // Fast forward past cert expiration time. 115 clk.Add(6 * time.Hour) 116 117 // Should be rejected as expired now. 118 _, err = checker.CheckCertificate(ctx, parsedCert) 119 So(err, ShouldErrLike, "certificate has expired") 120 121 // Generate a cert signed by a CA with the same name, but with different 122 // unexpected CA keys. 123 phonyCAKey, phonyCACert, err := generateCA(ctx, "Some CA: ca-name.fake") 124 So(err, ShouldBeNil) 125 certDer, err = generateCert(ctx, 3, "some-name", phonyCACert, phonyCAKey) 126 So(err, ShouldBeNil) 127 128 // CertChecker rejects it. 129 parsedCert, _ = x509.ParseCertificate(certDer) 130 _, err = checker.CheckCertificate(ctx, parsedCert) 131 So(err, ShouldErrLike, "crypto/rsa: verification error") 132 }) 133 } 134 135 func generateCA(c context.Context, name string) (*rsa.PrivateKey, []byte, error) { 136 // See https://golang.org/src/crypto/tls/generate_cert.go. 137 rand := cryptorand.Get(c) 138 privKey, err := rsa.GenerateKey(rand, 512) // use short key in tests 139 if err != nil { 140 return nil, nil, err 141 } 142 template := x509.Certificate{ 143 SerialNumber: big.NewInt(1), 144 Subject: pkix.Name{CommonName: name}, 145 NotBefore: clock.Now(c), 146 NotAfter: clock.Now(c).Add(30 * time.Hour), 147 IsCA: true, 148 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, 149 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 150 BasicConstraintsValid: true, 151 } 152 derBytes, err := x509.CreateCertificate(rand, &template, &template, privKey.Public(), privKey) 153 if err != nil { 154 return nil, nil, err 155 } 156 return privKey, derBytes, nil 157 } 158 159 func generateCert(c context.Context, sn int64, name string, caCert []byte, caKey *rsa.PrivateKey) ([]byte, error) { 160 parent, err := x509.ParseCertificate(caCert) 161 if err != nil { 162 return nil, err 163 } 164 rand := cryptorand.Get(c) 165 privKey, err := rsa.GenerateKey(rand, 512) // use short key in tests 166 if err != nil { 167 return nil, err 168 } 169 template := x509.Certificate{ 170 SerialNumber: big.NewInt(sn), 171 Subject: pkix.Name{CommonName: name}, 172 NotBefore: clock.Now(c), 173 NotAfter: clock.Now(c).Add(5 * time.Hour), 174 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 175 BasicConstraintsValid: true, 176 } 177 return x509.CreateCertificate(rand, &template, parent, privKey.Public(), caKey) 178 }