package x509_ops import ( "bufio" "bytes" "crypto" "crypto/rand" "crypto/rsa" "crypto/sha1" "crypto/x509" "crypto/x509/pkix" "encoding/base64" "encoding/hex" "encoding/pem" "fmt" "io" "io/ioutil" "math/big" "os" "path" "path/filepath" "strconv" "strings" "time" "github.com/longsleep/pkac" log "github.com/sirupsen/logrus" "git.cacert.org/cacert-gosigner/shared" "git.cacert.org/cacert-gosigner/signer/common" ) const crlLifetime = time.Hour * 24 * 7 var ( oidPkcs9EmailAddress = []int{1, 2, 840, 113549, 1, 9, 1} ) type Root struct { Name string privateKey crypto.Signer certificate *x509.Certificate databaseFile string serialNumberFile string crlNumberFile string crlFileName string crlHashDir string crlDistributionPoints []string ocspServers []string } func (x *Root) String() string { return x.Name } func loadCertificate(certificateFile string) (*x509.Certificate, error) { pemBytes, err := ioutil.ReadFile(certificateFile) if err != nil { return nil, fmt.Errorf( "could not load certificate %s: %v", certificateFile, err, ) } pemBlock, _ := pem.Decode(pemBytes) if pemBlock.Type != "CERTIFICATE" { log.Warnf( "PEM in %s is probably not a certificate. PEM block has type %s", certificateFile, pemBlock.Type, ) } certificate, err := x509.ParseCertificate(pemBlock.Bytes) if err != nil { return nil, fmt.Errorf( "could no parse certificate from %s: %v", certificateFile, err, ) } return certificate, nil } func loadPrivateKey(filename string) (crypto.Signer, error) { pemBytes, err := ioutil.ReadFile(filename) if err != nil { return nil, fmt.Errorf( "could not load private key %s: %v", filename, err, ) } pemBlock, _ := pem.Decode(pemBytes) if pemBlock == nil { return nil, fmt.Errorf("no PEM data found in %s", filename) } if pemBlock.Type != "PRIVATE KEY" { log.Warnf( "PEM in %s is probably not a private key. PEM block has type %s", filename, pemBlock.Type, ) } privateKey, err := x509.ParsePKCS8PrivateKey(pemBlock.Bytes) if err != nil { return nil, fmt.Errorf( "could no parse private key from %s: %v", filename, err, ) } return privateKey.(*rsa.PrivateKey), nil } func (x *Root) getNextSerialNumber() (*big.Int, error) { // TODO: decide whether we should use 64 bit random serial numbers as // recommended by CAB forum baseline requirements serialNumberFile := x.serialNumberFile _, err := os.Stat(serialNumberFile) if err != nil { log.Warnf("serial number file %s does not exist: %v", x.serialNumberFile, err) return big.NewInt(1), nil } data, err := ioutil.ReadFile(x.serialNumberFile) if err != nil { return nil, fmt.Errorf("could not read serial number file %s: %v", x.serialNumberFile, err) } result, err := common.StringAsBigInt(data) if err != nil { return nil, fmt.Errorf("could not parse content of %s as serial number: %v", x.serialNumberFile, err) } return result, err } func (x *Root) getNextCRLNumber() (*big.Int, error) { _, err := os.Stat(x.crlNumberFile) if err != nil { log.Warnf("CRL number file %s does not exist: %v", x.crlNumberFile, err) return big.NewInt(1), nil } data, err := ioutil.ReadFile(x.crlNumberFile) if err != nil { return nil, fmt.Errorf("could not read CRL number file %s: %v", x.crlNumberFile, err) } result, err := common.StringAsBigInt(data) if err != nil { return nil, fmt.Errorf("could not parse content of %s as CRL number: %v", x.crlNumberFile, err) } return result, nil } func (x *Root) bumpCRLNumber(current *big.Int) error { serial := current.Int64() + 1 crlNumberFile := x.crlNumberFile outFile, err := ioutil.TempFile(path.Dir(crlNumberFile), "*.txt") if err != nil { return fmt.Errorf("could not create temporary crl number file: %v", err) } defer func() { _ = outFile.Close() }() _, err = outFile.WriteString(fmt.Sprintf( "%s\n", strings.ToUpper(strconv.FormatInt(serial, 16)), )) if err != nil { return fmt.Errorf("could not write new CRL number %d to %s: %v", serial, outFile.Name(), err) } if err = outFile.Close(); err != nil { return fmt.Errorf("could not close temporary file %s: %v", outFile.Name(), err) } if err = os.Rename(crlNumberFile, fmt.Sprintf("%s.old", crlNumberFile)); err != nil { return fmt.Errorf("could not rename %s to %s.old: %v", crlNumberFile, crlNumberFile, err) } if err = os.Rename(outFile.Name(), crlNumberFile); err != nil { return fmt.Errorf("could not rename %s to %s: %v", outFile.Name(), crlNumberFile, err) } return nil } func (x *Root) bumpSerialNumber(current *big.Int) error { serial := current.Int64() + 1 outFile, err := ioutil.TempFile(path.Dir(x.serialNumberFile), "*.txt") if err != nil { return fmt.Errorf("could not open temporary serial number file: %v", err) } defer func() { _ = outFile.Close() }() _, err = outFile.WriteString(fmt.Sprintf( "%s\n", strings.ToUpper(strconv.FormatInt(serial, 16)), )) if err != nil { return fmt.Errorf("could not write new serial number %d to %s: %v", serial, outFile.Name(), err) } if err = outFile.Close(); err != nil { return fmt.Errorf("could not close temporary file %s: %v", outFile.Name(), err) } if _, err = os.Stat(x.serialNumberFile); err == nil { if err = os.Rename(x.serialNumberFile, fmt.Sprintf("%s.old", x.serialNumberFile)); err != nil { return fmt.Errorf("could not rename %s to %s.old: %v", x.serialNumberFile, x.serialNumberFile, err) } } if err = os.Rename(outFile.Name(), x.serialNumberFile); err != nil { return fmt.Errorf("could not rename %s to %s: %v", outFile.Name(), x.serialNumberFile, err) } return nil } func (x *Root) loadRevokedCertificatesFromDatabase() ([]pkix.RevokedCertificate, error) { databaseFile := x.databaseFile _, err := os.Stat(databaseFile) if err != nil { log.Warnf("openssl certificate database file %s does not exist: %v", databaseFile, err) return []pkix.RevokedCertificate{}, nil } file, err := os.Open(databaseFile) if err != nil { return nil, fmt.Errorf("could not open openssl certificate database file %s: %v", databaseFile, err) } defer func() { _ = file.Close() }() result := make([]pkix.RevokedCertificate, 0) scanner := bufio.NewScanner(file) for scanner.Scan() { line := strings.Split(scanner.Text(), "\t") if line[0] == "R" { serialNumber, err := common.StringAsBigInt([]byte(line[3])) if err != nil { return nil, fmt.Errorf("could not parse serial number %s as big int: %v", line[3], err) } revokeTs, err := strconv.ParseInt(line[2][:len(line[2])-1], 10, 64) if err != nil { return nil, fmt.Errorf("could not parse serial number: %v", err) } result = append(result, pkix.RevokedCertificate{ SerialNumber: serialNumber, RevocationTime: time.Unix(revokeTs, 0), Extensions: nil, }) } } return result, nil } func (x *Root) recordRevocation(certificate *x509.Certificate) (*pkix.RevokedCertificate, error) { _, err := os.Stat(x.databaseFile) if err != nil { return nil, fmt.Errorf("openssl certificate database file %s does not exist: %v", x.databaseFile, err) } inFile, err := os.Open(x.databaseFile) if err != nil { return nil, fmt.Errorf("could not open openssl certificate database file %s: %v", x.databaseFile, err) } defer func() { _ = inFile.Close() }() outFile, err := ioutil.TempFile(path.Dir(x.databaseFile), "*.txt") defer func() { _ = outFile.Close() }() if err != nil { return nil, fmt.Errorf("could not open temporary database file: %v", err) } scanner := bufio.NewScanner(inFile) writer := bufio.NewWriter(outFile) found := false revocationTime := time.Now() for scanner.Scan() { line := scanner.Text() parts := strings.Split(line, "\t") serialNumber, err := common.StringAsBigInt([]byte(parts[3])) if err != nil { return nil, fmt.Errorf("could not parse serial number %s as big int: %v", parts[3], err) } if serialNumber == certificate.SerialNumber { line = strings.Join( []string{"R", parts[1], strconv.FormatInt(revocationTime.Unix(), 10) + "Z", parts[3], parts[4]}, "\t", ) found = true } if _, err = writer.WriteString(fmt.Sprintf("%s\n", line)); err != nil { return nil, fmt.Errorf("could not write '%s' to %s: %v", line, outFile.Name(), err) } } if err = outFile.Close(); err != nil { return nil, fmt.Errorf("could not close temporary file %s: %v", outFile.Name(), err) } if err = inFile.Close(); err != nil { return nil, fmt.Errorf("could not close %s: %v", x.databaseFile, err) } if err = os.Rename(x.databaseFile, fmt.Sprintf("%s.old", x.databaseFile)); err != nil { return nil, fmt.Errorf("could not rename %s to %s.old: %v", x.databaseFile, x.databaseFile, err) } if err = os.Rename(outFile.Name(), x.databaseFile); err != nil { return nil, fmt.Errorf("could not rename temporary file %s to %s: %v", outFile.Name(), x.databaseFile, err) } if !found { log.Warnf("entry not found in database") } return &pkix.RevokedCertificate{ SerialNumber: certificate.SerialNumber, RevocationTime: revocationTime, }, nil } func (x *Root) RevokeCertificate(request []byte) (*pkix.RevokedCertificate, error) { pemBlock, _ := pem.Decode(request) if pemBlock.Type != "CERTIFICATE" { if pemBlock.Type != "CERTIFICATE" { log.Warnf( "PEM structure is probably not a certificate. PEM block has type %s", pemBlock.Type, ) log.Trace(request) } } certificate, err := x509.ParseCertificate(pemBlock.Bytes) if err != nil { return nil, fmt.Errorf( "could no parse certificate: %v", err, ) } return x.recordRevocation(certificate) } func (x *Root) GenerateCrl(algorithm x509.SignatureAlgorithm) ([]byte, *[20]byte, error) { certificatesToRevoke, err := x.loadRevokedCertificatesFromDatabase() if err != nil { return nil, nil, err } nextCrlNumber, err := x.getNextCRLNumber() if err != nil { return nil, nil, err } crlTemplate := &x509.RevocationList{ SignatureAlgorithm: algorithm, RevokedCertificates: certificatesToRevoke, Number: nextCrlNumber, ThisUpdate: time.Now(), NextUpdate: time.Now().Add(crlLifetime), ExtraExtensions: nil, } defer func() { if err = x.bumpCRLNumber(nextCrlNumber); err != nil { log.Errorf("could not bump CRL number: %v", err) } }() crlBytes, err := x509.CreateRevocationList( rand.Reader, crlTemplate, x.certificate, x.privateKey, ) if err != nil { return nil, nil, fmt.Errorf("could not create new CRL: %v", err) } if err = ioutil.WriteFile(x.crlFileName, crlBytes, 0644); err != nil { return nil, nil, fmt.Errorf("could not write new CRL to %s: %v", x.crlFileName, err) } newCrlHash := sha1.Sum(crlBytes) hashedCrlFileName := path.Join( x.crlHashDir, fmt.Sprintf("%s.crl", hex.EncodeToString(newCrlHash[:])), ) if err = ioutil.WriteFile(hashedCrlFileName, crlBytes, 0644); err != nil { return nil, nil, fmt.Errorf("could not write new CRL to %s: %v", hashedCrlFileName, err) } return crlBytes, &newCrlHash, nil } func (x *Root) DeleteOldCRLs(keepHashes ...string) error { log.Debugf("will look for CRLs in %s", x.crlHashDir) found, err := filepath.Glob(path.Join(x.crlHashDir, "*.crl")) if err != nil { log.Warnf("could not match files: %v", err) return nil } nextFound: for _, filename := range found { for _, keepHash := range keepHashes { if x.GetCrlFileName(keepHash) == filename { continue nextFound } } if err := os.Remove(filename); err != nil { return fmt.Errorf("could not delete %s: %v", filename, err) } } return nil } func (x *Root) GetCrlFileName(hash string) string { return path.Join(x.crlHashDir, fmt.Sprintf("%s.crl", hash)) } func (x *Root) checkPreconditions() { results := []bool{ x.checkFile(x.databaseFile, "database"), x.checkFile(x.serialNumberFile, "serial number"), x.checkFile(x.crlNumberFile, "CRL serial number"), x.checkFile(x.crlFileName, "CRL"), x.checkDir(x.crlHashDir, "directory for hash indexed CRLs"), } for _, success := range results { if !success { log.Warnf("preconditions for %s failed, operations may fail too", x) break } } } func (x *Root) checkFile(path, prefix string) bool { ok := true if s, e := os.Stat(path); e != nil { log.Warnf("%s file %s of %s has issues: %v", prefix, path, x, e) ok = false } else if s.IsDir() { log.Warnf("%s file %s of %s is a directory", prefix, path, x) ok = false } return ok } func (x *Root) checkDir(path, prefix string) bool { ok := true if s, e := os.Stat(path); e != nil { log.Warnf("%s %s of %s has issues: %v", prefix, path, x, e) if err := os.MkdirAll(path, 0755); err != nil { log.Warnf("could not create %s %s of %s: %v", prefix, path, x, err) } ok = false } else if !s.IsDir() { log.Warnf("%s %s of %s is not a directory", prefix, path, x) ok = false } return ok } type SigningRequestParameters struct { Request []byte Subject []byte SubjectAlternativeNames []byte Days uint16 IsSpkac bool } func (x *Root) SignCertificate( profile *Profile, algorithm x509.SignatureAlgorithm, params *SigningRequestParameters, ) ([]byte, error) { var publicKey interface{} if params.IsSpkac { var err error const spkacPrefix = "SPKAC=" if !bytes.Equal([]byte(spkacPrefix), params.Request[:len(spkacPrefix)]) { return nil, fmt.Errorf("request does not contain a valid SPKAC string") } derBytes, err := base64.StdEncoding.DecodeString(string(params.Request[len(spkacPrefix):])) if err != nil { return nil, fmt.Errorf("could not decode SPKAC bytes: %v", err) } publicKey, err = pkac.ParseSPKAC(derBytes) if err != nil { return nil, fmt.Errorf("could not parse SPKAC: %v", err) } } else { csrBlock, _ := pem.Decode(params.Request) if csrBlock.Type != "CERTIFICATE REQUEST" { return nil, fmt.Errorf("unexpected PEM block '%s' instead of 'CERTIFICATE REQUEST'", csrBlock.Type) } csr, err := x509.ParseCertificateRequest(csrBlock.Bytes) if err != nil { return nil, fmt.Errorf("could not parse CSR: %v", err) } publicKey = csr.PublicKey } nextSerialNumber, err := x.getNextSerialNumber() if err != nil { return nil, fmt.Errorf("could not get next serial number: %v", err) } // copy profile notBefore := time.Now() notAfter := notBefore.Add(time.Hour * 24 * time.Duration(params.Days)) certificate := &x509.Certificate{ KeyUsage: profile.prototype.KeyUsage, ExtKeyUsage: profile.prototype.ExtKeyUsage, SignatureAlgorithm: algorithm, CRLDistributionPoints: x.crlDistributionPoints, OCSPServer: x.ocspServers, SerialNumber: nextSerialNumber, NotBefore: notBefore, NotAfter: notAfter, } // check subject subject, err := profile.parseSubject(params.Subject) if err != nil { return nil, fmt.Errorf("could not parse subject: %v", err) } certificate.Subject = *subject // check altNames err = profile.parseAltNames(certificate, params.SubjectAlternativeNames) if err != nil { return nil, fmt.Errorf("could not parse subject alternative names: %v", err) } moveEmailsFromSubjectToAlternativeNames(certificate) log.Tracef("prepared for signing: %+v", certificate) certBytes, err := x509.CreateCertificate(rand.Reader, certificate, x.certificate, publicKey, x.privateKey) if err != nil { return nil, fmt.Errorf("could not sign certificate: %v", err) } parsedCertificate, err := x509.ParseCertificate(certBytes) if err != nil { return nil, fmt.Errorf("could not parse signed certificate: %v", err) } if err = x.bumpSerialNumber(nextSerialNumber); err != nil { log.Errorf("could not bump serial number: %v", err) } err = x.recordIssuedCertificate(parsedCertificate) if err != nil { return nil, fmt.Errorf("could not record signed certificate in database: %v", err) } pemBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes}) log.Tracef("signed new certificate\n%s", pemBytes) return pemBytes, nil } // we move the email addresses to alt names because they are encoded to UTF-8 // otherwise which is not compliant to RFC-5280 func moveEmailsFromSubjectToAlternativeNames(certificate *x509.Certificate) { extraNames := make([]pkix.AttributeTypeAndValue, 0) for _, p := range certificate.Subject.ExtraNames { if p.Type.Equal(oidPkcs9EmailAddress) { email := p.Value.(string) if certificate.EmailAddresses == nil { certificate.EmailAddresses = []string{email} continue } for _, e := range certificate.EmailAddresses { if e == p.Value { continue } } certificate.EmailAddresses = append(certificate.EmailAddresses, email) } else { extraNames = append(extraNames, p) } } certificate.Subject.ExtraNames = extraNames } func (x *Root) recordIssuedCertificate(certificate *x509.Certificate) error { log.Tracef("recording %+v", certificate) tempFile, err := ioutil.TempFile(path.Dir(x.databaseFile), "*.txt") if err != nil { return fmt.Errorf("could not create temporary file: %v", err) } defer func() { _ = tempFile.Close() }() tempName := tempFile.Name() dbExists := false _, err = os.Stat(x.databaseFile) if err != nil { log.Warnf("openssl certificate database file %s does not exist: %v", x.databaseFile, err) } else { dbExists = true inFile, err := os.Open(x.databaseFile) defer func() { _ = inFile.Close() }() if err != nil { return fmt.Errorf("could not open openssl certificate database file %s: %v", x.databaseFile, err) } _, err = io.Copy(tempFile, inFile) if err != nil { return fmt.Errorf("could not copy %s to temporary file %s: %v", x.databaseFile, tempName, err) } if err = inFile.Close(); err != nil { return fmt.Errorf("could not close %s: %v", x.databaseFile, err) } } if err = tempFile.Close(); err != nil { return fmt.Errorf("could not close temporary file %s: %v", tempName, err) } outFile, err := os.OpenFile(tempName, os.O_APPEND|os.O_WRONLY, 0644) if err != nil { return fmt.Errorf("could not open temporary file for writing %s: %v", tempName, err) } defer func() { _ = outFile.Close() }() line := strings.Join([]string{"V", strconv.FormatInt(certificate.NotBefore.Unix(), 10) + "Z", "", strings.ToUpper(certificate.SerialNumber.Text(16)), "unknown", opensslFormatDN(certificate.Subject)}, "\t") _, err = fmt.Fprintln(outFile, line) if err != nil { return fmt.Errorf("could not write '%s' to %s: %v", line, tempName, err) } if err = outFile.Close(); err != nil { return fmt.Errorf("could not close temporary file %s: %v", tempName, err) } if dbExists { if err = os.Rename(x.databaseFile, fmt.Sprintf("%s.old", x.databaseFile)); err != nil { return fmt.Errorf("could not rename %s to %s.old: %v", x.databaseFile, x.databaseFile, err) } } if err = os.Rename(tempName, x.databaseFile); err != nil { return fmt.Errorf("could not rename temporary file %s to %s: %v", tempName, x.databaseFile, err) } return nil } func opensslFormatDN(subject pkix.Name) string { var buf strings.Builder for _, rdn := range subject.ToRDNSequence() { if len(rdn) == 0 { continue } for _, atv := range rdn { value, ok := atv.Value.(string) if !ok { continue } t := atv.Type if len(t) == 4 && t[:3].Equal([]int{2, 5, 4}) { switch t[3] { case 3: buf.WriteString("/CN=") buf.WriteString(value) case 6: buf.WriteString("/C=") buf.WriteString(value) case 7: buf.WriteString("/L=") buf.WriteString(value) case 8: buf.WriteString("/ST=") buf.WriteString(value) case 10: buf.WriteString("/O=") buf.WriteString(value) case 11: buf.WriteString("/OU=") buf.WriteString(value) } } else if t.Equal(oidPkcs9EmailAddress) { buf.WriteString("/emailAddress=") buf.WriteString(value) } } } return buf.String() } func NewRoot( basedir, name, subdir string, id shared.CryptoSystemRootId, crlDistributionPoints, ocspServers []string, ) *Root { key, err := loadPrivateKey(path.Join(basedir, subdir, "private", "ca.key.pem")) if err != nil { log.Fatalf("could not load private key: %v", err) } cert, err := loadCertificate(path.Join(basedir, subdir, "ca.crt.pem")) if err != nil { log.Fatalf("could not load CA certificate: %v", err) } root := &Root{ Name: name, privateKey: key, certificate: cert, databaseFile: path.Join(basedir, subdir, "index.txt"), serialNumberFile: path.Join(basedir, subdir, "serial"), crlNumberFile: path.Join(basedir, subdir, "crlnumber"), crlFileName: fmt.Sprintf("revoke-root%d.crl", id), crlHashDir: path.Join( basedir, "currentcrls", fmt.Sprintf("%d", id), ), crlDistributionPoints: crlDistributionPoints, ocspServers: ocspServers, } root.checkPreconditions() return root } type AltNameType string const ( NameTypeDNS AltNameType = "DNS" NameTypeXmppJid AltNameType = "otherName:1.3.6.1.5.5.7.8.5;UTF8" // from RFC 3920, 6120 ) type SubjectDnField string const ( SubjectDnFieldCountryName SubjectDnField = "C" SubjectDnFieldStateOrProvinceName SubjectDnField = "ST" SubjectDnFieldLocalityName SubjectDnField = "L" SubjectDnFieldOrganizationName SubjectDnField = "O" SubjectDnFieldOrganizationalUnitName SubjectDnField = "OU" SubjectDnFieldCommonName SubjectDnField = "CN" SubjectDnFieldEmailAddress SubjectDnField = "emailAddress" ) type Profile struct { name string prototype *x509.Certificate subjectDNFields []SubjectDnField altNameTypes []AltNameType copyEmail bool // whether to copy emailAddress values from subjectDN to email address subject alternative name } func (p *Profile) String() string { return p.name } func (p *Profile) parseSubject(subject []byte) (*pkix.Name, error) { parts := strings.Split(string(subject), "/") subjectDN := &pkix.Name{} for _, part := range parts { if len(strings.TrimSpace(part)) == 0 { continue } handled := false item := strings.SplitN(part, "=", 2) for _, f := range p.subjectDNFields { if !strings.EqualFold(item[0], string(f)) { continue } value := item[1] handled = true switch f { case SubjectDnFieldCountryName: if subjectDN.Country == nil { subjectDN.Country = []string{value} } else { subjectDN.Country = append(subjectDN.Country, value) } case SubjectDnFieldStateOrProvinceName: if subjectDN.Province == nil { subjectDN.Province = []string{value} } else { subjectDN.Province = append(subjectDN.Province, value) } case SubjectDnFieldLocalityName: if subjectDN.Locality == nil { subjectDN.Locality = []string{value} } else { subjectDN.Locality = append(subjectDN.Locality, value) } case SubjectDnFieldOrganizationName: if subjectDN.Organization == nil { subjectDN.Organization = []string{value} } else { subjectDN.Organization = append(subjectDN.Organization, value) } case SubjectDnFieldOrganizationalUnitName: if subjectDN.OrganizationalUnit == nil { subjectDN.OrganizationalUnit = []string{value} } else { subjectDN.OrganizationalUnit = append(subjectDN.OrganizationalUnit, value) } case SubjectDnFieldCommonName: subjectDN.CommonName = value case SubjectDnFieldEmailAddress: emailIA5 := pkix.AttributeTypeAndValue{ Type: oidPkcs9EmailAddress, Value: value, } if subjectDN.ExtraNames == nil { subjectDN.ExtraNames = []pkix.AttributeTypeAndValue{emailIA5} } else { subjectDN.ExtraNames = append(subjectDN.ExtraNames, emailIA5) } default: log.Warnf("unhandled subject DN type %s", f) } } if !handled { return nil, fmt.Errorf("skipped part %s because it is not supported by profile %s", part, p) } } log.Debugf("created subject DN %s", subjectDN) return subjectDN, nil } func (p *Profile) parseAltNames(template *x509.Certificate, altNames []byte) error { parts := strings.Split(string(altNames), ",") for _, part := range parts { if len(strings.TrimSpace(part)) == 0 { continue } handled := false item := strings.SplitN(part, ":", 3) if item[0] == "otherName" { item = []string{strings.Join(item[:2], ":"), item[2]} } else { item = []string{item[0], strings.Join(item[1:], ":")} } for _, f := range p.altNameTypes { if item[0] != string(f) { continue } value := item[1] handled = true switch f { case NameTypeDNS: if template.DNSNames == nil { template.DNSNames = []string{value} } else { template.DNSNames = append(template.DNSNames, value) } case NameTypeXmppJid: // x509.Certificate has no support for otherName alternative names log.Warnf("skipping %s because it cannot be supported", part) default: log.Warnf("unhandled alternative name type %s", f) } } if !handled { return fmt.Errorf("skipped alternative name %s because it is not supported by profile %s", part, p) } } return nil } func NewProfile( name string, prototype *x509.Certificate, subjectDnFields []SubjectDnField, altNameTypes []AltNameType, copyEmail bool, ) *Profile { return &Profile{ name: name, prototype: prototype, subjectDNFields: subjectDnFields, altNameTypes: altNameTypes, copyEmail: copyEmail, } }