blob: 21c31160a64d791dfbfb04d3ef68cc57033e43e5 [file] [log] [blame]
package deliveryservice
/*
* 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/base64"
"encoding/pem"
"errors"
"math/big"
"time"
"github.com/apache/trafficcontrol/lib/go-util"
)
const NewCertValidDuration = time.Hour * 24 * 365
// GenerateCert generates a key and certificate for serving HTTPS. The generated key is 2048-bit RSA, to match the old Perl code.
// The certificate will be valid for NewCertValidDuration time after now.
// Returns PEM-encoded certificate signing request (csr), certificate (crt), and key; or any error.
func GenerateCert(host, country, city, state, org, unit string) ([]byte, []byte, []byte, error) {
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, nil, errors.New("generating key: " + err.Error())
}
now := time.Now()
expires := now.Add(NewCertValidDuration)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return nil, nil, nil, errors.New("getting random int for serial number: " + err.Error())
}
subj := pkix.Name{
CommonName: host,
Country: []string{country},
Province: []string{state},
Locality: []string{city},
Organization: []string{org},
OrganizationalUnit: []string{unit},
}
crt := x509.Certificate{
SerialNumber: serialNumber,
Subject: subj,
NotBefore: now,
NotAfter: expires,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
DNSNames: []string{host},
Version: 1,
}
crtReq := x509.CertificateRequest{
Subject: subj,
SignatureAlgorithm: x509.SHA256WithRSA,
Version: 1,
}
crtDer, err := x509.CreateCertificate(rand.Reader, &crt, &crt, &priv.PublicKey, priv)
if err != nil {
return nil, nil, nil, errors.New("creating certificate: " + err.Error())
}
crtBuf := bytes.Buffer{}
if err := pem.Encode(&crtBuf, &pem.Block{Type: "CERTIFICATE", Bytes: crtDer}); err != nil {
return nil, nil, nil, errors.New("pem-encoding certificate: " + err.Error())
}
crtPem := crtBuf.Bytes()
csrDer, err := x509.CreateCertificateRequest(rand.Reader, &crtReq, priv)
if err != nil {
return nil, nil, nil, errors.New("creating certificate request: " + err.Error())
}
csrBuf := bytes.Buffer{}
if err := pem.Encode(&csrBuf, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrDer}); err != nil {
return nil, nil, nil, errors.New("pem-encoding certificate request: " + err.Error())
}
csrPem := csrBuf.Bytes()
keyDer := x509.MarshalPKCS1PrivateKey(priv)
if keyDer == nil {
return nil, nil, nil, errors.New("marshalling private key: nil der")
}
keyBuf := bytes.Buffer{}
if err := pem.Encode(&keyBuf, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyDer}); err != nil {
return nil, nil, nil, errors.New("pem-encoding private key: " + err.Error())
}
keyPem := keyBuf.Bytes()
return EncodePEMToLegacyPerlRiakFormat(csrPem), EncodePEMToLegacyPerlRiakFormat(crtPem), EncodePEMToLegacyPerlRiakFormat(keyPem), nil
}
// EncodePEMToLegacyPerlRiakFormat takes a PEM-encoded byte (typically a certificate, csr, or key) and returns the format Perl Traffic Ops used to send to Riak.
func EncodePEMToLegacyPerlRiakFormat(pem []byte) []byte {
b64Pem := []byte(base64.StdEncoding.EncodeToString(pem)) // Why are we base64-encoding a base64-encoded format? Because Perl
b64Lines := util.BytesLenSplit(b64Pem, 76) // Why 76? Because Perl
joined := bytes.Join(b64Lines, []byte{'\n'}) // Why are we joining arbitrary base64-encoded characters with newlines? Because Perl
return joined
}