experimental/certificate_auth/certs/generate_certs.go (318 lines of code) (raw):

package main /* * 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. */ import ( "bytes" "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/pem" "flag" "fmt" "io/ioutil" "log" "math" "math/big" "net" "time" ) // CertificateKeyPair contains the parsed representation of a certificate // and private key. type CertificateKeyPair struct { Certificate *x509.Certificate PrivateKey *rsa.PrivateKey } // CertificatePEMPair contains the PEM encoded certificate and private key. type CertificatePEMPair struct { CertificatePEM, PrivateKeyPEM string } var ( rootCN = "root.local" interCN = "intermediate.local" clientCN = "client.local" serverCN = "server.local" uid = "userid" // useEcdsa = false //TODO: Enable and refactor ) func main() { flag.StringVar(&uid, "uid", uid, "[Optional] The User ID value to be added to the client certificate") flag.Parse() rootCAPEMPair, err := GenerateRootCACertificate() if err != nil { log.Fatalf("Failed to generate and sign Root CA certificate\nErr: %s\n", err) } ioutil.WriteFile("rootca.crt.pem", []byte(rootCAPEMPair.CertificatePEM), 0644) ioutil.WriteFile("rootca.key.pem", []byte(rootCAPEMPair.PrivateKeyPEM), 0644) intermediatePEMPair, err := GenerateIntermediateCertificate(rootCAPEMPair) if err != nil { log.Fatalf("Failed to generate and sign Intermediate certificate\nErr: %s\n", err) } ioutil.WriteFile("intermediate.crt.pem", []byte(intermediatePEMPair.CertificatePEM), 0644) ioutil.WriteFile("intermediate.key.pem", []byte(intermediatePEMPair.PrivateKeyPEM), 0644) serverPEMPair, err := GenerateServerCertificate(intermediatePEMPair) if err != nil { log.Fatalf("Failed to generate and sign Server certificate\nErr: %s\n", err) } ioutil.WriteFile("server.crt.pem", []byte(serverPEMPair.CertificatePEM), 0644) ioutil.WriteFile("server.key.pem", []byte(serverPEMPair.PrivateKeyPEM), 0644) clientPEMPair, err := GenerateClientCertificate(intermediatePEMPair) if err != nil { log.Fatalf("Failed to generate and sign Client certificate\nErr: %s\n", err) } ioutil.WriteFile("client.crt.pem", []byte(clientPEMPair.CertificatePEM), 0644) ioutil.WriteFile("client.key.pem", []byte(clientPEMPair.PrivateKeyPEM), 0644) clientIntermediateChain := clientPEMPair.CertificatePEM + intermediatePEMPair.CertificatePEM ioutil.WriteFile("client-intermediate-chain.crt.pem", []byte(clientIntermediateChain), 0644) if err := VerifyCertificates(rootCAPEMPair, intermediatePEMPair, clientPEMPair, serverPEMPair); err != nil { log.Fatalf("failed to verify certificate: %s", err) } } // ParseCertificateKeyPair decodes the provided PEM pair (key, cert) and returns a // parsed private key and x509 certificate. func ParseCertificateKeyPair(pemPair *CertificatePEMPair) (*CertificateKeyPair, error) { keyPair := new(CertificateKeyPair) privPemBlock, _ := pem.Decode([]byte(pemPair.PrivateKeyPEM)) privateKey, err := x509.ParsePKCS8PrivateKey(privPemBlock.Bytes) if err != nil { return nil, fmt.Errorf("failed to parse private key: %w", err) } keyPair.PrivateKey = privateKey.(*rsa.PrivateKey) certPemBlock, _ := pem.Decode([]byte(pemPair.CertificatePEM)) certificate, err := x509.ParseCertificate(certPemBlock.Bytes) if err != nil { return nil, fmt.Errorf("failed to parse public key: %w", err) } keyPair.Certificate = certificate return keyPair, nil } // GenereateRootCACertificate creates a Root CA certificate that can be used // for signing intermediate, client, and server x509 certificates. func GenerateRootCACertificate() (*CertificatePEMPair, error) { now := time.Now() serialNumber, _ := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) cert := &x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ OrganizationalUnit: []string{"ATC"}, Organization: []string{"Apache"}, Country: []string{"US"}, Province: []string{"Colorado"}, Locality: []string{"Denver"}, CommonName: rootCN, }, NotBefore: now, NotAfter: now.AddDate(1, 0, 0), KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign | x509.KeyUsageCRLSign, BasicConstraintsValid: true, IsCA: true, } certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { return nil, fmt.Errorf("failed to generate RSA key: %w", err) } certDERBytes, err := x509.CreateCertificate(rand.Reader, cert, cert, &certPrivKey.PublicKey, certPrivKey) if err != nil { return nil, fmt.Errorf("failed to create certificate: %w", err) } certPEMPair := new(CertificatePEMPair) certPEM := new(bytes.Buffer) pem.Encode(certPEM, &pem.Block{ Type: "CERTIFICATE", Bytes: certDERBytes, }) certPrivKeyPEM := new(bytes.Buffer) certPrivKeyByes, err := x509.MarshalPKCS8PrivateKey(certPrivKey) if err != nil { return nil, fmt.Errorf("failed to marshal private key to PKCS8: %w", err) } pem.Encode(certPrivKeyPEM, &pem.Block{ Type: "PRIVATE KEY", Bytes: certPrivKeyByes, }) certPEMPair.CertificatePEM = certPEM.String() certPEMPair.PrivateKeyPEM = certPrivKeyPEM.String() return certPEMPair, nil } // GenerateIntermediateCeertificate creates an intermediate based on the provided Root certificate. // This certificate can be used for signing client and server certificates to establish // a chain to the Root certificate. func GenerateIntermediateCertificate(root *CertificatePEMPair) (*CertificatePEMPair, error) { rootKeyPair, err := ParseCertificateKeyPair(root) if err != nil { log.Fatalln("Failed to parse root cert and key") } now := time.Now() serialNumber, _ := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) cert := &x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ OrganizationalUnit: []string{"ATC"}, Organization: []string{"Apache"}, Country: []string{"US"}, Province: []string{"Colorado"}, Locality: []string{"Denver"}, CommonName: interCN, }, NotBefore: now, NotAfter: now.AddDate(1, 0, 0), ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign | x509.KeyUsageCRLSign, BasicConstraintsValid: true, MaxPathLenZero: true, IsCA: true, } certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { return nil, fmt.Errorf("failed to generate RSA key: %w", err) } certDERBytes, err := x509.CreateCertificate(rand.Reader, cert, rootKeyPair.Certificate, &certPrivKey.PublicKey, rootKeyPair.PrivateKey) if err != nil { return nil, fmt.Errorf("failed to create certificate: %w", err) } certPEMPair := new(CertificatePEMPair) certPEM := new(bytes.Buffer) pem.Encode(certPEM, &pem.Block{ Type: "CERTIFICATE", Bytes: certDERBytes, }) certPrivKeyPEM := new(bytes.Buffer) certPrivKeyByes, err := x509.MarshalPKCS8PrivateKey(certPrivKey) if err != nil { return nil, fmt.Errorf("failed to marshal private key to PKCS8: %w", err) } pem.Encode(certPrivKeyPEM, &pem.Block{ Type: "PRIVATE KEY", Bytes: certPrivKeyByes, }) certPEMPair.CertificatePEM = certPEM.String() certPEMPair.PrivateKeyPEM = certPrivKeyPEM.String() return certPEMPair, nil } // GenerateClientCertificate creates and signs a certificate based on the provided RootCA. This differs // from the Server certificate in that it includes the OID for LDAP UID as well as Client Auth key usage. // // Currently the key is an RSA key, which also entails adding KeyEncipherment key usage. func GenerateClientCertificate(intermediate *CertificatePEMPair) (*CertificatePEMPair, error) { intermediateKeyPair, err := ParseCertificateKeyPair(intermediate) if err != nil { log.Fatalln("Failed to parse root cert and key") } now := time.Now() serialNumber, _ := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) // LDAP OID reference: https://ldap.com/ldap-oid-reference-guide/ // 0.9.2342.19200300.100.1.1 uid Attribute Type uidPkix := pkix.AttributeTypeAndValue{ Type: asn1.ObjectIdentifier([]int{0, 9, 2342, 19200300, 100, 1, 1}), Value: uid, } cert := &x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ OrganizationalUnit: []string{"ATC"}, Organization: []string{"Apache"}, Country: []string{"US"}, Province: []string{"Colorado"}, Locality: []string{"Denver"}, CommonName: clientCN, ExtraNames: []pkix.AttributeTypeAndValue{uidPkix}, }, NotBefore: now, NotAfter: now.AddDate(1, 0, 0), ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyAgreement, } certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { return nil, fmt.Errorf("failed to generate RSA key: %w", err) } certDERBytes, err := x509.CreateCertificate(rand.Reader, cert, intermediateKeyPair.Certificate, &certPrivKey.PublicKey, intermediateKeyPair.PrivateKey) if err != nil { return nil, fmt.Errorf("failed to create certificate: %w", err) } certPEMPair := new(CertificatePEMPair) certPEM := new(bytes.Buffer) pem.Encode(certPEM, &pem.Block{ Type: "CERTIFICATE", Bytes: certDERBytes, }) certPrivKeyPEM := new(bytes.Buffer) certPrivKeyByes, err := x509.MarshalPKCS8PrivateKey(certPrivKey) if err != nil { return nil, fmt.Errorf("failed to marshal private key to PKCS8: %w", err) } pem.Encode(certPrivKeyPEM, &pem.Block{ Type: "PRIVATE KEY", Bytes: certPrivKeyByes, }) certPEMPair.CertificatePEM = certPEM.String() certPEMPair.PrivateKeyPEM = certPrivKeyPEM.String() return certPEMPair, nil } // GenerateServerCertificate creates and signs a certificate based on the provided RootCA. This differs // from the Client certificate in that it ServerAuth key usage. It also does NOT include the OID for LDAP UID. // // Currently the key is an RSA key, which also entails adding KeyEncipherment key usage. func GenerateServerCertificate(intermediate *CertificatePEMPair) (*CertificatePEMPair, error) { intermediateKeyPair, err := ParseCertificateKeyPair(intermediate) if err != nil { log.Fatalln("Failed to parse root cert and key") } now := time.Now() serialNumber, _ := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) cert := &x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ OrganizationalUnit: []string{"ATC"}, Organization: []string{"Apache"}, Country: []string{"US"}, Province: []string{"Colorado"}, Locality: []string{"Denver"}, CommonName: serverCN, }, DNSNames: []string{serverCN}, IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback}, NotBefore: now, NotAfter: now.AddDate(1, 0, 0), ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement, } certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { return nil, fmt.Errorf("failed to generate RSA key: %w", err) } certDERBytes, err := x509.CreateCertificate(rand.Reader, cert, intermediateKeyPair.Certificate, &certPrivKey.PublicKey, intermediateKeyPair.PrivateKey) if err != nil { return nil, fmt.Errorf("failed to create certificate: %w", err) } certPEMPair := new(CertificatePEMPair) certPEM := new(bytes.Buffer) pem.Encode(certPEM, &pem.Block{ Type: "CERTIFICATE", Bytes: certDERBytes, }) certPrivKeyPEM := new(bytes.Buffer) certPrivKeyByes, err := x509.MarshalPKCS8PrivateKey(certPrivKey) if err != nil { return nil, fmt.Errorf("failed to marshal private key to PKCS8: %w", err) } pem.Encode(certPrivKeyPEM, &pem.Block{ Type: "PRIVATE KEY", Bytes: certPrivKeyByes, }) certPEMPair.CertificatePEM = certPEM.String() certPEMPair.PrivateKeyPEM = certPrivKeyPEM.String() return certPEMPair, nil } // VerifyCertificates checks that the client and server certificates match the // Root and Intermediate chains. func VerifyCertificates(root, intermediate, client, server *CertificatePEMPair) error { rootKeyPair, err := ParseCertificateKeyPair(root) if err != nil { log.Fatalln("Failed to parse root cert and key") } intermediateKeyPair, err := ParseCertificateKeyPair(intermediate) if err != nil { log.Fatalln("Failed to parse intermediate cert and key") } rootPool := x509.NewCertPool() rootPool.AddCert(rootKeyPair.Certificate) intermediatePool := x509.NewCertPool() intermediatePool.AddCert(intermediateKeyPair.Certificate) opts := x509.VerifyOptions{ Intermediates: intermediatePool, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, Roots: rootPool, } clientCert, err := ParseCertificateKeyPair(client) if err != nil { return fmt.Errorf("failed to parse client cert and key: %w", err) } if _, err := clientCert.Certificate.Verify(opts); err != nil { return fmt.Errorf("failed to verify client cert and key: %w", err) } serverCert, err := ParseCertificateKeyPair(server) if err != nil { return fmt.Errorf("failed to parse server cert and key: %w", err) } if _, err := serverCert.Certificate.Verify(opts); err != nil { return fmt.Errorf("failed to verify client cert and key: %w", err) } return nil }