go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/auth/signing/certs_test.go (about)

     1  // Copyright 2015 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 signing
    16  
    17  import (
    18  	"context"
    19  	"encoding/pem"
    20  	"fmt"
    21  	"net/http"
    22  	"testing"
    23  
    24  	"go.chromium.org/luci/server/auth/internal"
    25  	"go.chromium.org/luci/server/caching"
    26  
    27  	. "github.com/smartystreets/goconvey/convey"
    28  	. "go.chromium.org/luci/common/testing/assertions"
    29  )
    30  
    31  var certBlob = `-----BEGIN CERTIFICATE-----
    32  MIIBDjCBu6ADAgECAgEBMAsGCSqGSIb3DQEBCzAAMCAXDTAxMDkwOTAxNDY0MFoY
    33  DzIyODYxMTIwMTc0NjQwWjAAMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMGYtc/k
    34  vp1Sr2zZFWPu534tqX9chKxhADlLbPR4A+ojKl/EchYCV6DE7Ikogx02PFpYZe3A
    35  3a4hccSufwr3wtMCAwEAAaMgMB4wDgYDVR0PAQH/BAQDAgCAMAwGA1UdEwEB/wQC
    36  MAAwCwYJKoZIhvcNAQELA0EAI/3v5eWNzA2oudenR8Vo5EY0j3zCUVhlHRErlcUR
    37  I69yAHZUpJ9lzcwmHcaCJ76m/jDINZrYoL/4aSlDEGgHmw==
    38  -----END CERTIFICATE-----
    39  `
    40  
    41  func TestFetchCertificates(t *testing.T) {
    42  	t.Parallel()
    43  
    44  	const testURL = "https://test.example.com"
    45  
    46  	Convey("With empty cache", t, func() {
    47  		ctx := caching.WithEmptyProcessCache(context.Background())
    48  
    49  		Convey("Works", func() {
    50  			ctx := internal.WithTestTransport(ctx, func(r *http.Request, body string) (int, string) {
    51  				So(r.URL.String(), ShouldEqual, testURL)
    52  				return 200, fmt.Sprintf(`{
    53  				"service_account_name": "blah@blah.com",
    54  				"certificates": [{
    55  					"key_name": "abc",
    56  					"x509_certificate_pem": %q
    57  				}],
    58  				"timestamp": 1446166229439210
    59  			}`, certBlob)
    60  			})
    61  
    62  			certs, err := FetchCertificates(ctx, testURL)
    63  			So(err, ShouldBeNil)
    64  			So(certs.ServiceAccountName, ShouldEqual, "blah@blah.com")
    65  			So(len(certs.Certificates), ShouldEqual, 1)
    66  		})
    67  
    68  		Convey("Errors", func() {
    69  			ctx := internal.WithTestTransport(ctx, func(r *http.Request, body string) (int, string) {
    70  				return 401, "fail"
    71  			})
    72  
    73  			_, err := FetchCertificates(ctx, testURL)
    74  			So(err, ShouldNotBeNil)
    75  		})
    76  
    77  		Convey("Bad JSON", func() {
    78  			ctx := internal.WithTestTransport(ctx, func(r *http.Request, body string) (int, string) {
    79  				return 200, fmt.Sprintf(`{
    80  				"certificates": [{
    81  					"key_name": "abc",
    82  					"x509_certificate_pem": %q
    83  				}],
    84  				"timestamp": "not an int"
    85  			}`, certBlob)
    86  			})
    87  
    88  			_, err := FetchCertificates(ctx, testURL)
    89  			So(err, ShouldNotBeNil)
    90  		})
    91  	})
    92  }
    93  
    94  func TestFetchCertificatesForServiceAccount(t *testing.T) {
    95  	t.Parallel()
    96  
    97  	Convey("Works", t, func() {
    98  		ctx := caching.WithEmptyProcessCache(context.Background())
    99  		ctx = internal.WithTestTransport(ctx, func(r *http.Request, body string) (int, string) {
   100  			So(r.URL.String(), ShouldEqual, "https://www.googleapis.com/robot/v1/metadata/x509/robot%40robots.gserviceaccount.com")
   101  			return 200, `{
   102  				"0392f9886770640357cbb29e57d3698291b1e805": "-----BEGIN CERTIFICATE-----\nblah 1\n-----END CERTIFICATE-----\n",
   103  				"f5db308971078d1496c262cc06b6e7f87652af55": "-----BEGIN CERTIFICATE-----\nblah 2\n-----END CERTIFICATE-----\n"
   104  			}`
   105  		})
   106  
   107  		certs, err := FetchCertificatesForServiceAccount(ctx, "robot@robots.gserviceaccount.com")
   108  		So(err, ShouldBeNil)
   109  		So(certs.ServiceAccountName, ShouldEqual, "robot@robots.gserviceaccount.com")
   110  		So(certs.Certificates, ShouldResemble, []Certificate{
   111  			{
   112  				KeyName:            "0392f9886770640357cbb29e57d3698291b1e805",
   113  				X509CertificatePEM: "-----BEGIN CERTIFICATE-----\nblah 1\n-----END CERTIFICATE-----\n",
   114  			},
   115  			{
   116  				KeyName:            "f5db308971078d1496c262cc06b6e7f87652af55",
   117  				X509CertificatePEM: "-----BEGIN CERTIFICATE-----\nblah 2\n-----END CERTIFICATE-----\n",
   118  			},
   119  		})
   120  	})
   121  }
   122  
   123  func TestFetchGoogleOAuth2Certificates(t *testing.T) {
   124  	t.Parallel()
   125  
   126  	Convey("Works", t, func() {
   127  		ctx := caching.WithEmptyProcessCache(context.Background())
   128  		ctx = internal.WithTestTransport(ctx, func(r *http.Request, body string) (int, string) {
   129  			So(r.URL.String(), ShouldEqual, "https://www.googleapis.com/oauth2/v1/certs")
   130  			return 200, `{
   131  				"0392f9886770640357cbb29e57d3698291b1e805": "-----BEGIN CERTIFICATE-----\nblah 1\n-----END CERTIFICATE-----\n",
   132  				"f5db308971078d1496c262cc06b6e7f87652af55": "-----BEGIN CERTIFICATE-----\nblah 2\n-----END CERTIFICATE-----\n"
   133  			}`
   134  		})
   135  
   136  		certs, err := FetchGoogleOAuth2Certificates(ctx)
   137  		So(err, ShouldBeNil)
   138  		So(certs.Certificates, ShouldResemble, []Certificate{
   139  			{
   140  				KeyName:            "0392f9886770640357cbb29e57d3698291b1e805",
   141  				X509CertificatePEM: "-----BEGIN CERTIFICATE-----\nblah 1\n-----END CERTIFICATE-----\n",
   142  			},
   143  			{
   144  				KeyName:            "f5db308971078d1496c262cc06b6e7f87652af55",
   145  				X509CertificatePEM: "-----BEGIN CERTIFICATE-----\nblah 2\n-----END CERTIFICATE-----\n",
   146  			},
   147  		})
   148  	})
   149  }
   150  
   151  func TestCertificateForKey(t *testing.T) {
   152  	t.Parallel()
   153  
   154  	Convey("Works", t, func() {
   155  		certs := PublicCertificates{
   156  			Certificates: []Certificate{
   157  				{
   158  					KeyName:            "abc",
   159  					X509CertificatePEM: certBlob,
   160  				},
   161  			},
   162  		}
   163  		cert, err := certs.CertificateForKey("abc")
   164  		So(err, ShouldBeNil)
   165  		So(cert, ShouldNotBeNil)
   166  
   167  		// Code coverage for cache hit.
   168  		cert, err = certs.CertificateForKey("abc")
   169  		So(err, ShouldBeNil)
   170  		So(cert, ShouldNotBeNil)
   171  	})
   172  
   173  	Convey("Bad PEM", t, func() {
   174  		certs := PublicCertificates{
   175  			Certificates: []Certificate{
   176  				{
   177  					KeyName:            "abc",
   178  					X509CertificatePEM: "not a pem",
   179  				},
   180  			},
   181  		}
   182  		cert, err := certs.CertificateForKey("abc")
   183  		So(err, ShouldErrLike, "not PEM")
   184  		So(cert, ShouldBeNil)
   185  	})
   186  
   187  	Convey("Bad cert", t, func() {
   188  		certs := PublicCertificates{
   189  			Certificates: []Certificate{
   190  				{
   191  					KeyName: "abc",
   192  					X509CertificatePEM: string(pem.EncodeToMemory(&pem.Block{
   193  						Type:  "CERTIFICATE",
   194  						Bytes: []byte("not a certificate"),
   195  					})),
   196  				},
   197  			},
   198  		}
   199  		cert, err := certs.CertificateForKey("abc")
   200  		So(err, ShouldNotBeNil)
   201  		So(cert, ShouldBeNil)
   202  	})
   203  
   204  	Convey("Missing key", t, func() {
   205  		certs := PublicCertificates{}
   206  		cert, err := certs.CertificateForKey("abc")
   207  		So(err, ShouldErrLike, "no such certificate")
   208  		So(cert, ShouldBeNil)
   209  	})
   210  }
   211  
   212  func TestCheckSignature(t *testing.T) {
   213  	// See signingtest/signer_test.go for where this cert and signature were
   214  	// generated. 'signingtest' module itself can't be imported due to import
   215  	// cycle.
   216  
   217  	t.Parallel()
   218  
   219  	Convey("Works", t, func() {
   220  		certs := PublicCertificates{
   221  			Certificates: []Certificate{
   222  				{
   223  					KeyName:            "abc",
   224  					X509CertificatePEM: certBlob,
   225  				},
   226  			},
   227  		}
   228  
   229  		blob := []byte("some blob")
   230  
   231  		signature := []byte{
   232  			0x66, 0x2d, 0xa6, 0xa0, 0x65, 0x63, 0x8b, 0x83, 0xc5, 0x45, 0xeb, 0xfd,
   233  			0x88, 0xec, 0x9, 0x41, 0x59, 0x92, 0xd0, 0x48, 0x78, 0x37, 0xc2, 0x45,
   234  			0x74, 0xfc, 0x8b, 0x13, 0xa, 0xca, 0x47, 0x7d, 0xd1, 0x24, 0x2c, 0x6c,
   235  			0xbe, 0x3a, 0xea, 0xc5, 0x12, 0x76, 0xb4, 0xe1, 0xa9, 0x4a, 0x40, 0x40,
   236  			0x24, 0xf7, 0x1e, 0x7c, 0x91, 0x91, 0xe3, 0x71, 0x4f, 0x21, 0xf4, 0xe4,
   237  			0xec, 0x65, 0x87, 0x1c,
   238  		}
   239  
   240  		err := certs.CheckSignature("abc", blob, signature)
   241  		So(err, ShouldBeNil)
   242  
   243  		err = certs.CheckSignature("abc", blob, []byte{1, 2, 3})
   244  		So(err, ShouldNotBeNil)
   245  
   246  		err = certs.CheckSignature("no key", blob, signature)
   247  		So(err, ShouldNotBeNil)
   248  	})
   249  }