go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/tokenserver/client/x509signer.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 client 16 17 import ( 18 "context" 19 "crypto" 20 "crypto/rsa" 21 "crypto/x509" 22 "encoding/pem" 23 "fmt" 24 "os" 25 "sync" 26 27 "go.chromium.org/luci/common/data/rand/cryptorand" 28 ) 29 30 // TODO(vadimsh): Adding support for ECDSA should be trivial if needed. 31 32 // X509Signer implements Signer interface by using a private key and 33 // a certificate specified in ANS.1 x509 PEM encoded structures. 34 // 35 // It is fine to initialize this struct directly if you have loaded private key 36 // and certificate already. You can optionally use Validate() to make sure they 37 // are valid before making other calls (all calls do validation anyhow). 38 // 39 // Use LoadX509Signer to load the key and certificate from files on disk. 40 type X509Signer struct { 41 // PrivateKeyPEM is PEM-encoded ASN.1 PKCS#1 private key. 42 // 43 // See https://openssl.org/docs/manmaster/apps/rsa.html. 44 PrivateKeyPEM []byte 45 46 // CertificatePEM is PEM-encoded ASN.1 x509 certificate. 47 // 48 // It must contain a public key matching the private key specified by 49 // PrivateKeyPEM (this will be verified). 50 // 51 // See https://openssl.org/docs/manmaster/apps/x509.html. 52 CertificatePEM []byte 53 54 // Fields below are lazy initialized on first use in Validate(). 55 56 init sync.Once 57 err error 58 algo x509.SignatureAlgorithm 59 certDer []byte 60 pkey crypto.Signer 61 } 62 63 // LoadX509Signer parses and validates private key and certificate PEM files. 64 // 65 // Returns X509Signer that is ready for work. 66 func LoadX509Signer(privateKeyPath, certPath string) (*X509Signer, error) { 67 pkey, err := os.ReadFile(privateKeyPath) 68 if err != nil { 69 return nil, err 70 } 71 cert, err := os.ReadFile(certPath) 72 if err != nil { 73 return nil, err 74 } 75 signer := &X509Signer{ 76 PrivateKeyPEM: pkey, 77 CertificatePEM: cert, 78 } 79 if err = signer.Validate(); err != nil { 80 return nil, err 81 } 82 return signer, nil 83 } 84 85 // Algo returns an algorithm that the signer implements. 86 func (s *X509Signer) Algo(ctx context.Context) (x509.SignatureAlgorithm, error) { 87 if err := s.Validate(); err != nil { 88 return 0, err 89 } 90 return s.algo, nil 91 } 92 93 // Certificate returns ASN.1 DER blob with the certificate of the signer. 94 func (s *X509Signer) Certificate(ctx context.Context) ([]byte, error) { 95 if err := s.Validate(); err != nil { 96 return nil, err 97 } 98 return s.certDer, nil 99 } 100 101 // Sign signs a blob using the private key. 102 func (s *X509Signer) Sign(ctx context.Context, blob []byte) ([]byte, error) { 103 if err := s.Validate(); err != nil { 104 return nil, err 105 } 106 107 var hashFunc crypto.Hash 108 switch s.algo { 109 case x509.SHA256WithRSA: 110 hashFunc = crypto.SHA256 111 default: 112 panic("someone forgot to implement hashing algo for new kind of a key") 113 } 114 115 h := hashFunc.New() 116 h.Write(blob) 117 digest := h.Sum(nil) 118 119 return s.pkey.Sign(cryptorand.Get(ctx), digest, hashFunc) 120 } 121 122 // Validate parses the private key and certificate file and verifies them. 123 // 124 // It checks that the public portion of the key matches what's in the 125 // certificate. 126 func (s *X509Signer) Validate() error { 127 s.init.Do(func() { 128 s.err = s.initialize() 129 }) 130 return s.err 131 } 132 133 func (s *X509Signer) initialize() error { 134 pkey, err := parsePrivateKey(s.PrivateKeyPEM) 135 if err != nil { 136 return err 137 } 138 cert, certDer, err := parseCertificate(s.CertificatePEM) 139 if err != nil { 140 return err 141 } 142 143 // Make sure the private key matches the public key in the cert. Also pick 144 // the corresponding signing algorithm based on the type of the key. 145 var algo x509.SignatureAlgorithm 146 switch key := pkey.(type) { 147 case *rsa.PrivateKey: 148 var pub *rsa.PublicKey 149 if pub, _ = cert.PublicKey.(*rsa.PublicKey); pub == nil { 150 return fmt.Errorf("the certificate doesn't match the private key - wrong types") 151 } 152 if key.PublicKey.E != pub.E || key.PublicKey.N.Cmp(pub.N) != 0 { 153 return fmt.Errorf("the certificate doesn't match the private key") 154 } 155 algo = x509.SHA256WithRSA 156 default: 157 panic("someone forgot to implement public key check for new kind of a key") 158 } 159 160 s.algo = algo 161 s.certDer = certDer 162 s.pkey = pkey 163 return nil 164 } 165 166 func parsePrivateKey(pemData []byte) (crypto.Signer, error) { 167 block, rest := pem.Decode(pemData) 168 if len(rest) != 0 || block == nil { 169 return nil, fmt.Errorf("not a valid private key PEM") 170 } 171 switch block.Type { 172 case "RSA PRIVATE KEY": 173 return x509.ParsePKCS1PrivateKey(block.Bytes) 174 default: 175 return nil, fmt.Errorf("not a supported private key type - %q", block.Type) 176 } 177 } 178 179 func parseCertificate(pemData []byte) (*x509.Certificate, []byte, error) { 180 block, rest := pem.Decode(pemData) 181 if len(rest) != 0 || block == nil { 182 return nil, nil, fmt.Errorf("not a valid certificate PEM") 183 } 184 if block.Type != "CERTIFICATE" { 185 return nil, nil, fmt.Errorf("expecting \"CERTIFICATE\" PEM, got %q", block.Type) 186 } 187 cert, err := x509.ParseCertificate(block.Bytes) 188 if err != nil { 189 return nil, nil, err 190 } 191 return cert, block.Bytes, err 192 }