| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 issuer |
| |
| import ( |
| "crypto" |
| "crypto/rand" |
| "crypto/tls" |
| "crypto/x509" |
| "fmt" |
| "math/big" |
| "net/url" |
| "time" |
| ) |
| |
| import ( |
| "github.com/pkg/errors" |
| |
| "github.com/spiffe/go-spiffe/v2/spiffeid" |
| ) |
| |
| import ( |
| mesh_proto "github.com/apache/dubbo-kubernetes/api/mesh/v1alpha1" |
| "github.com/apache/dubbo-kubernetes/pkg/core" |
| util_tls "github.com/apache/dubbo-kubernetes/pkg/tls" |
| util_rsa "github.com/apache/dubbo-kubernetes/pkg/util/rsa" |
| ) |
| |
| const ( |
| DefaultAllowedClockSkew = 10 * time.Second |
| DefaultWorkloadCertValidityPeriod = 24 * time.Hour |
| ) |
| |
| type CertOptsFn = func(*x509.Certificate) |
| |
| func WithExpirationTime(expiration time.Duration) CertOptsFn { |
| return func(certificate *x509.Certificate) { |
| now := core.Now() |
| certificate.NotAfter = now.Add(expiration) |
| } |
| } |
| |
| func NewWorkloadCert(ca util_tls.KeyPair, mesh string, tags mesh_proto.MultiValueTagSet, certOpts ...CertOptsFn) (*util_tls.KeyPair, error) { |
| caPrivateKey, caCert, err := loadKeyPair(ca) |
| if err != nil { |
| return nil, errors.Wrap(err, "failed to load CA key pair") |
| } |
| |
| workloadKey, err := util_rsa.GenerateKey(util_rsa.DefaultKeySize) |
| if err != nil { |
| return nil, errors.Wrap(err, "failed to generate a private key") |
| } |
| template, err := newWorkloadTemplate(mesh, tags, workloadKey.Public(), certOpts...) |
| if err != nil { |
| return nil, errors.Wrap(err, "failed to generate X509 certificate template") |
| } |
| workloadCert, err := x509.CreateCertificate(rand.Reader, template, caCert, workloadKey.Public(), caPrivateKey) |
| if err != nil { |
| return nil, errors.Wrap(err, "failed to generate X509 certificate") |
| } |
| return util_tls.ToKeyPair(workloadKey, workloadCert) |
| } |
| |
| func newWorkloadTemplate(trustDomain string, tags mesh_proto.MultiValueTagSet, publicKey crypto.PublicKey, certOpts ...CertOptsFn) (*x509.Certificate, error) { |
| var uris []*url.URL |
| for _, service := range tags.Values(mesh_proto.ServiceTag) { |
| domain, err := spiffeid.TrustDomainFromString(trustDomain) |
| if err != nil { |
| return nil, err |
| } |
| uri, err := spiffeid.FromSegments(domain, service) |
| if err != nil { |
| return nil, err |
| } |
| uris = append(uris, uri.URL()) |
| } |
| for _, tag := range tags.Keys() { |
| for _, value := range tags.UniqueValues(tag) { |
| uri := fmt.Sprintf("dubbo://%s/%s", tag, value) |
| u, err := url.Parse(uri) |
| if err != nil { |
| return nil, errors.Wrap(err, "invalid Dubbo URI") |
| } |
| uris = append(uris, u) |
| } |
| } |
| |
| now := time.Now() |
| serialNumber, err := newSerialNumber() |
| if err != nil { |
| return nil, err |
| } |
| |
| template := &x509.Certificate{ |
| SerialNumber: serialNumber, |
| // Subject is deliberately left empty |
| URIs: uris, |
| NotBefore: now.Add(-DefaultAllowedClockSkew), |
| NotAfter: now.Add(DefaultWorkloadCertValidityPeriod), |
| KeyUsage: x509.KeyUsageKeyEncipherment | |
| x509.KeyUsageKeyAgreement | |
| x509.KeyUsageDigitalSignature, |
| ExtKeyUsage: []x509.ExtKeyUsage{ |
| x509.ExtKeyUsageServerAuth, |
| x509.ExtKeyUsageClientAuth, |
| }, |
| BasicConstraintsValid: true, |
| PublicKey: publicKey, |
| } |
| |
| for _, opt := range certOpts { |
| opt(template) |
| } |
| return template, nil |
| } |
| |
| var maxUint128, one *big.Int |
| |
| func init() { |
| one = big.NewInt(1) |
| m := new(big.Int) |
| m.Lsh(one, 128) |
| maxUint128 = m.Sub(m, one) |
| } |
| |
| func newSerialNumber() (*big.Int, error) { |
| res, err := rand.Int(rand.Reader, maxUint128) |
| if err != nil { |
| return nil, fmt.Errorf("failed generation of serial number: %w", err) |
| } |
| // Because we generate in the range [0, maxUint128) and 0 is an invalid serial and maxUint128 is valid we add 1 |
| // to have a number in range [1, maxUint128] See: https://cabforum.org/2016/03/31/ballot-164/ |
| return res.Add(res, one), nil |
| } |
| |
| func loadKeyPair(pair util_tls.KeyPair) (crypto.PrivateKey, *x509.Certificate, error) { |
| root, err := tls.X509KeyPair(pair.CertPEM, pair.KeyPEM) |
| if err != nil { |
| return nil, nil, errors.Wrap(err, "failed to parse TLS key pair") |
| } |
| rootCert, err := x509.ParseCertificate(root.Certificate[0]) |
| if err != nil { |
| return nil, nil, errors.Wrap(err, "failed to parse X509 certificate") |
| } |
| return root.PrivateKey, rootCert, nil |
| } |