mirror of
https://github.com/square/certigo.git
synced 2024-04-21 12:32:40 +00:00
Co-authored-by: John Wood <jwood@squareup.com> Co-authored-by: Sven Wick <sven.wick@gmx.de>
365 lines
10 KiB
Go
365 lines
10 KiB
Go
/*-
|
|
* Copyright 2016 Square Inc.
|
|
*
|
|
* Licensed 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 lib
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"fmt"
|
|
"text/template"
|
|
"time"
|
|
|
|
"crypto/x509/pkix"
|
|
|
|
"github.com/Masterminds/sprig"
|
|
"github.com/fatih/color"
|
|
)
|
|
|
|
var verboseLayout = `
|
|
{{- define "PkixName" -}}
|
|
{{- range .Names}}
|
|
{{ .Type | oidName }}: {{ .Value }}
|
|
{{- end -}}
|
|
{{end -}}
|
|
|
|
{{- if .Alias}}{{.Alias}}
|
|
{{end}}Serial: {{.SerialNumber}}
|
|
Valid: {{.NotBefore | certStart}} to {{.NotAfter | certEnd}}
|
|
Signature: {{.SignatureAlgorithm | highlightAlgorithm}}{{if .IsSelfSigned}} (self-signed){{end}}
|
|
Subject Info:
|
|
{{- template "PkixName" .Subject.Name}}
|
|
Issuer Info:
|
|
{{- template "PkixName" .Issuer.Name}}
|
|
{{- if .Subject.KeyID}}
|
|
Subject Key ID: {{.Subject.KeyID | hexify}}
|
|
{{- end}}
|
|
{{- if .Issuer.KeyID}}
|
|
Authority Key ID: {{.Issuer.KeyID | hexify}}
|
|
{{- end}}
|
|
{{- if .BasicConstraints}}
|
|
Basic Constraints: CA:{{.BasicConstraints.IsCA}}{{if .BasicConstraints.MaxPathLen}}, pathlen:{{.BasicConstraints.MaxPathLen}}{{end}}{{end}}
|
|
{{- if .NameConstraints}}
|
|
Name Constraints{{if .NameConstraints.Critical}} (critical){{end}}:
|
|
{{- if .NameConstraints.PermittedDNSDomains}}
|
|
Permitted DNS domains:
|
|
{{wrapWith .Width "\n\t" (join ", " .NameConstraints.PermittedDNSDomains)}}
|
|
{{- end -}}
|
|
{{- if .NameConstraints.PermittedEmailAddresses}}
|
|
Permitted email addresses:
|
|
{{wrapWith .Width "\n\t" (join ", " .NameConstraints.PermittedEmailAddresses)}}
|
|
{{- end -}}
|
|
{{- if .NameConstraints.PermittedIPRanges}}
|
|
Permitted IP ranges:
|
|
{{wrapWith .Width "\n\t" (join ", " .NameConstraints.PermittedIPRanges)}}
|
|
{{- end -}}
|
|
{{- if .NameConstraints.PermittedURIDomains}}
|
|
Permitted URI domains:
|
|
{{wrapWith .Width "\n\t" (join ", " .NameConstraints.PermittedURIDomains)}}
|
|
{{- end}}
|
|
{{- if .NameConstraints.ExcludedDNSDomains}}
|
|
Excluded DNS domains:
|
|
{{wrapWith .Width "\n\t" (join ", " .NameConstraints.ExcludedDNSDomains)}}
|
|
{{- end}}
|
|
{{- if .NameConstraints.ExcludedEmailAddresses}}
|
|
Excluded email addresses:
|
|
{{wrapWith .Width "\n\t" (join ", " .NameConstraints.ExcludedEmailAddresses)}}
|
|
{{- end}}
|
|
{{- if .NameConstraints.ExcludedIPRanges}}
|
|
Excluded IP ranges:
|
|
{{wrapWith .Width "\n\t" (join ", " .NameConstraints.ExcludedIPRanges)}}
|
|
{{- end}}
|
|
{{- if .NameConstraints.ExcludedURIDomains}}
|
|
Excluded URI domains:
|
|
{{wrapWith .Width "\n\t" (join ", " .NameConstraints.ExcludedURIDomains)}}
|
|
{{- end}}
|
|
{{- end}}
|
|
{{- if .OCSPServer}}
|
|
OCSP Server(s):
|
|
{{wrapWith .Width "\n\t" (join ", " .OCSPServer)}}
|
|
{{- end}}
|
|
{{- if .IssuingCertificateURL}}
|
|
Issuing Certificate URL(s):
|
|
{{wrapWith .Width "\n\t" (join ", " .IssuingCertificateURL)}}
|
|
{{- end}}
|
|
{{- if .KeyUsage}}
|
|
Key Usage:
|
|
{{- range .KeyUsage | keyUsage}}
|
|
{{.}}
|
|
{{- end}}
|
|
{{- end}}
|
|
{{- if .ExtKeyUsage}}
|
|
Extended Key Usage:
|
|
{{- range .ExtKeyUsage}}
|
|
{{. | extKeyUsage}}{{end}}
|
|
{{- end}}
|
|
{{- if .AltDNSNames}}
|
|
DNS Names:
|
|
{{wrapWith .Width "\n\t" (join ", " .AltDNSNames)}}
|
|
{{- end}}
|
|
{{- if .AltIPAddresses}}
|
|
IP Addresses:
|
|
{{wrapWith .Width "\n\t" (join ", " .AltIPAddresses)}}
|
|
{{- end}}
|
|
{{- if .URINames}}
|
|
URI Names:
|
|
{{wrapWith .Width "\n\t" (join ", " .URINames)}}
|
|
{{- end}}
|
|
{{- if .EmailAddresses}}
|
|
Email Addresses:
|
|
{{wrapWith .Width "\n\t" (join ", " .EmailAddresses)}}
|
|
{{- end}}
|
|
{{- range .SCTList}}
|
|
Signed Certificate Timestamp:
|
|
Version: {{.Version | sctVersion}}
|
|
{{- if .LogOperator}}
|
|
Log Operator: {{.LogOperator}}
|
|
{{- end}}
|
|
{{- if .LogURL}}
|
|
Log URL: {{.LogURL}}
|
|
{{- end}}
|
|
Log ID: {{wrapWith 48 "\n\t " (.LogID | hexify)}}
|
|
Timestamp: {{.Timestamp | formatTime}}
|
|
Signature: {{.SignatureAlgorithm | highlightAlgorithm}}
|
|
{{- end}}
|
|
{{- if .Warnings}}
|
|
Lints:
|
|
{{- range .Warnings}}
|
|
{{. | redify}}
|
|
{{- end}}
|
|
{{- end}}`
|
|
|
|
var layout = `
|
|
{{- if .Alias}}{{.Alias}}
|
|
{{end -}}
|
|
Valid: {{.NotBefore | certStart}} to {{.NotAfter | certEnd}}
|
|
Subject:
|
|
{{wrapWith .Width "\n\t" (.Subject.Name | printShortName)}}
|
|
Issuer:
|
|
{{wrapWith .Width "\n\t" (.Issuer.Name | printShortName)}}
|
|
{{- if .AltDNSNames}}
|
|
DNS Names:
|
|
{{wrapWith .Width "\n\t" (join ", " .AltDNSNames)}}{{end}}
|
|
{{- if .AltIPAddresses}}
|
|
IP Addresses:
|
|
{{wrapWith .Width "\n\t" (join ", " .AltIPAddresses)}}{{end}}
|
|
{{- if .URINames}}
|
|
URI Names:
|
|
{{wrapWith .Width "\n\t" (join ", " .URINames)}}{{end}}
|
|
{{- if .EmailAddresses}}
|
|
Email Addresses:
|
|
{{wrapWith .Width "\n\t" (join ", " .EmailAddresses)}}{{end}}
|
|
{{- if .Warnings}}
|
|
Lints:{{range .Warnings}}
|
|
{{. | redify}}{{end}}{{end}}`
|
|
|
|
// EncodeX509ToJSON encodes an X.509 certificate into a JSON string.
|
|
func EncodeX509ToJSON(cert *x509.Certificate) []byte {
|
|
out := createSimpleCertificate("", cert)
|
|
raw, err := json.Marshal(out)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return raw
|
|
}
|
|
|
|
// EncodeX509ToObject encodes an X.509 certificate into a JSON-serializable object.
|
|
func EncodeX509ToObject(cert *x509.Certificate) interface{} {
|
|
return createSimpleCertificate("", cert)
|
|
}
|
|
|
|
// EncodeX509ToText encodes an X.509 certificate into human-readable text.
|
|
func EncodeX509ToText(cert *x509.Certificate, terminalWidth int, verbose bool) []byte {
|
|
c := createSimpleCertificate("", cert)
|
|
c.Width = terminalWidth - 8 /* Need some margin for tab */
|
|
|
|
return displayCert(c, verbose)
|
|
}
|
|
|
|
// displayCert takes in a parsed certificate object
|
|
// (for jceks certs, blank otherwise), and prints out relevant
|
|
// information. Start and end dates are colored based on whether or not
|
|
// the certificate is expired, not expired, or close to expiring.
|
|
func displayCert(cert simpleCertificate, verbose bool) []byte {
|
|
// Use template functions from sprig, but add some extras
|
|
funcMap := sprig.TxtFuncMap()
|
|
|
|
extras := template.FuncMap{
|
|
"certStart": certStart,
|
|
"certEnd": certEnd,
|
|
"redify": redify,
|
|
"highlightAlgorithm": highlightAlgorithm,
|
|
"hexify": hexify,
|
|
"keyUsage": keyUsage,
|
|
"extKeyUsage": extKeyUsage,
|
|
"oidName": oidName,
|
|
"oidShort": oidShort,
|
|
"printShortName": PrintShortName,
|
|
"printCommonName": PrintCommonName,
|
|
"formatTime": formatTime,
|
|
"sctVersion": sctVersion,
|
|
}
|
|
for k, v := range extras {
|
|
funcMap[k] = v
|
|
}
|
|
|
|
t := template.New("Cert template").Funcs(funcMap)
|
|
var err error
|
|
if verbose {
|
|
t, err = t.Parse(verboseLayout)
|
|
} else {
|
|
t, err = t.Parse(layout)
|
|
}
|
|
if err != nil {
|
|
// Should never happen
|
|
panic(err)
|
|
}
|
|
var buffer bytes.Buffer
|
|
w := bufio.NewWriter(&buffer)
|
|
err = t.Execute(w, cert)
|
|
if err != nil {
|
|
// Should never happen
|
|
panic(err)
|
|
}
|
|
w.Flush()
|
|
return buffer.Bytes()
|
|
}
|
|
|
|
var (
|
|
green = color.New(color.Bold, color.FgGreen)
|
|
yellow = color.New(color.Bold, color.FgYellow)
|
|
red = color.New(color.Bold, color.FgRed)
|
|
)
|
|
|
|
var algorithmColors = map[x509.SignatureAlgorithm]*color.Color{
|
|
x509.MD2WithRSA: red,
|
|
x509.MD5WithRSA: red,
|
|
x509.SHA1WithRSA: red,
|
|
x509.SHA256WithRSA: green,
|
|
x509.SHA384WithRSA: green,
|
|
x509.SHA512WithRSA: green,
|
|
x509.DSAWithSHA1: red,
|
|
x509.DSAWithSHA256: red,
|
|
x509.ECDSAWithSHA1: red,
|
|
x509.ECDSAWithSHA256: green,
|
|
x509.ECDSAWithSHA384: green,
|
|
x509.ECDSAWithSHA512: green,
|
|
}
|
|
|
|
// highlightAlgorithm changes the color of the signing algorithm
|
|
// based on a set color map, e.g. to make SHA-1 show up red.
|
|
func highlightAlgorithm(sigAlg simpleSigAlg) string {
|
|
sig := x509.SignatureAlgorithm(sigAlg)
|
|
color, ok := algorithmColors[sig]
|
|
if !ok {
|
|
return algString(sig)
|
|
}
|
|
return color.SprintFunc()(algString(sig))
|
|
}
|
|
|
|
// timeString formats a time in UTC with minute precision, in the given color.
|
|
func timeString(t time.Time, c *color.Color) string {
|
|
return c.SprintfFunc()(t.Format("2006-01-02 15:04 MST"))
|
|
}
|
|
|
|
func formatTime(t time.Time) string {
|
|
return t.Format("2006-01-02 15:04 MST")
|
|
}
|
|
|
|
// certStart takes a given start time for the validity of
|
|
// a certificate and returns that time colored properly
|
|
// based on how close it is to expiry. If it's more than
|
|
// a day after the certificate became valid the string will
|
|
// be green. If it has been less than a day the string will
|
|
// be yellow. If the certificate is not yet valid, the string
|
|
// will be red.
|
|
func certStart(start time.Time) string {
|
|
now := time.Now()
|
|
day, _ := time.ParseDuration("24h")
|
|
threshold := start.Add(day)
|
|
if now.After(threshold) {
|
|
return timeString(start, green)
|
|
} else if now.After(start) {
|
|
return timeString(start, yellow)
|
|
} else {
|
|
return timeString(start, red)
|
|
}
|
|
}
|
|
|
|
// certEnd takes a given end time for the validity of
|
|
// a certificate and returns that time colored properly
|
|
// based on how close it is to expiry. If the certificate
|
|
// is more than a month away from expiring it returns a
|
|
// green string. If the certificate is less than a month
|
|
// from expiry it returns a yellow string. If the certificate
|
|
// is expired it returns a red string.
|
|
func certEnd(end time.Time) string {
|
|
now := time.Now()
|
|
month, _ := time.ParseDuration("720h")
|
|
threshold := now.Add(month)
|
|
if threshold.Before(end) {
|
|
return timeString(end, green)
|
|
} else if now.Before(end) {
|
|
return timeString(end, yellow)
|
|
} else {
|
|
return timeString(end, red)
|
|
}
|
|
}
|
|
|
|
func redify(text string) string {
|
|
return red.SprintfFunc()("%s", text)
|
|
}
|
|
|
|
func greenify(text string) string {
|
|
return green.SprintfFunc()("%s", text)
|
|
}
|
|
|
|
// PrintCommonName prints the CN from a pkix.Name, or falls back to PrintShortName if CN is missing.
|
|
func PrintCommonName(name pkix.Name) (out string) {
|
|
if name.CommonName != "" {
|
|
return fmt.Sprintf("CN=%s", name.CommonName)
|
|
}
|
|
return PrintShortName(name)
|
|
}
|
|
|
|
// PrintShortName turns a pkix.Name into a string of RDN tuples.
|
|
func PrintShortName(name pkix.Name) (out string) {
|
|
printed := false
|
|
for _, name := range name.Names {
|
|
short := oidShort(name.Type)
|
|
if short != "" {
|
|
if printed {
|
|
out += ", "
|
|
}
|
|
out += fmt.Sprintf("%s=%v", short, name.Value)
|
|
printed = true
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func sctVersion(v uint64) string {
|
|
switch v {
|
|
case 0:
|
|
return "0 (v1)"
|
|
default:
|
|
return red.SprintFunc()("%d (unknown)", v)
|
|
}
|
|
}
|