Add ski utility.
This commit is contained in:
parent
c8f839de73
commit
a3ead16faf
|
@ -0,0 +1,30 @@
|
||||||
|
ski: print subject public key info
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
ski [-hm] files...
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-h Print a help message and exit.
|
||||||
|
-m All SKIs should match.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
Printing the SKI of a private key and certificate:
|
||||||
|
|
||||||
|
$ ski *
|
||||||
|
server.key 3A:AB:D1:B2:E5:7A:F2:5A:D5:8E:8B:7B:25:D9:41:90:F8:6B:A3:5E (RSA private key)
|
||||||
|
[ski] trailing data in PEM file
|
||||||
|
server.pem 3A:AB:D1:B2:E5:7A:F2:5A:D5:8E:8B:7B:25:D9:41:90:F8:6B:A3:5E (RSA certificate)
|
||||||
|
|
||||||
|
Making sure the SKIs match:
|
||||||
|
|
||||||
|
$ ski -m *
|
||||||
|
tyrfingr.key 3A:AB:D1:B2:E5:7A:F2:5A:D5:8E:8B:7B:25:D9:41:90:F8:6B:A3:5E (RSA private key)
|
||||||
|
[ski] trailing data in PEM file
|
||||||
|
tyrfingr.pem 3A:AB:D1:B2:E5:7A:F2:5A:D5:8E:8B:7B:25:D9:41:90:F8:6B:A3:5E (RSA certificate)
|
||||||
|
|
||||||
|
Making sure the SKIs match with a bad certificate:
|
||||||
|
$ ski -m server.key bad.pem
|
||||||
|
server.key 3A:AB:D1:B2:E5:7A:F2:5A:D5:8E:8B:7B:25:D9:41:90:F8:6B:A3:5E (RSA private key)
|
||||||
|
[ski] bad.pem: SKI mismatch (3A:AB:D1:B2:E5:7A:F2:5A:D5:8E:8B:7B:25:D9:41:90:F8:6B:A3:5E != 90:AF:6A:3A:94:5A:0B:D8:90:EA:12:56:73:DF:43:B4:3A:28:DA:E7)
|
||||||
|
bad.pem 90:AF:6A:3A:94:5A:0B:D8:90:EA:12:56:73:DF:43:B4:3A:28:DA:E7 (RSA certificate)
|
|
@ -0,0 +1,187 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/asn1"
|
||||||
|
"encoding/pem"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/kisom/goutils/die"
|
||||||
|
"github.com/kisom/goutils/lib"
|
||||||
|
)
|
||||||
|
|
||||||
|
func usage(w io.Writer) {
|
||||||
|
fmt.Fprintf(w, `ski: print subject key info for PEM-encoded files
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
ski [-hm] files...
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-h Print this help message.
|
||||||
|
-m All SKIs should match; as soon as an SKI mismatch is found,
|
||||||
|
it is reported.
|
||||||
|
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Usage = func() { usage(os.Stderr) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse(path string) (public []byte, kt, ft string) {
|
||||||
|
data, err := ioutil.ReadFile(path)
|
||||||
|
die.If(err)
|
||||||
|
|
||||||
|
data = bytes.TrimSpace(data)
|
||||||
|
p, rest := pem.Decode(data)
|
||||||
|
if len(rest) > 0 {
|
||||||
|
lib.Warnx("trailing data in PEM file")
|
||||||
|
}
|
||||||
|
|
||||||
|
data = p.Bytes
|
||||||
|
|
||||||
|
switch p.Type {
|
||||||
|
case "PRIVATE KEY", "RSA PRIVATE KEY", "EC PRIVATE KEY":
|
||||||
|
public, kt = parseKey(data)
|
||||||
|
ft = "private key"
|
||||||
|
case "CERTIFICATE":
|
||||||
|
public, kt = parseCertificate(data)
|
||||||
|
ft = "certificate"
|
||||||
|
case "CERTIFICATE REQUEST":
|
||||||
|
public, kt = parseCSR(data)
|
||||||
|
ft = "certificate request"
|
||||||
|
default:
|
||||||
|
die.With("unknown PEM type %s", p.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseKey(data []byte) (public []byte, kt string) {
|
||||||
|
privInterface, err := x509.ParsePKCS8PrivateKey(data)
|
||||||
|
if err != nil {
|
||||||
|
privInterface, err = x509.ParsePKCS1PrivateKey(data)
|
||||||
|
if err != nil {
|
||||||
|
privInterface, err = x509.ParseECPrivateKey(data)
|
||||||
|
if err != nil {
|
||||||
|
die.With("couldn't parse private key.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var priv crypto.Signer
|
||||||
|
switch privInterface.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
priv = privInterface.(*rsa.PrivateKey)
|
||||||
|
kt = "RSA"
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
priv = privInterface.(*ecdsa.PrivateKey)
|
||||||
|
kt = "ECDSA"
|
||||||
|
default:
|
||||||
|
die.With("unknown private key type %T", privInterface)
|
||||||
|
}
|
||||||
|
|
||||||
|
public, err = x509.MarshalPKIXPublicKey(priv.Public())
|
||||||
|
die.If(err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCertificate(data []byte) (public []byte, kt string) {
|
||||||
|
cert, err := x509.ParseCertificate(data)
|
||||||
|
die.If(err)
|
||||||
|
|
||||||
|
pub := cert.PublicKey
|
||||||
|
switch pub.(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
kt = "RSA"
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
kt = "ECDSA"
|
||||||
|
default:
|
||||||
|
die.With("unknown public key type %T", pub)
|
||||||
|
}
|
||||||
|
|
||||||
|
public, err = x509.MarshalPKIXPublicKey(pub)
|
||||||
|
die.If(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCSR(data []byte) (public []byte, kt string) {
|
||||||
|
csr, err := x509.ParseCertificateRequest(data)
|
||||||
|
die.If(err)
|
||||||
|
|
||||||
|
pub := csr.PublicKey
|
||||||
|
switch pub.(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
kt = "RSA"
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
kt = "ECDSA"
|
||||||
|
default:
|
||||||
|
die.With("unknown public key type %T", pub)
|
||||||
|
}
|
||||||
|
|
||||||
|
public, err = x509.MarshalPKIXPublicKey(pub)
|
||||||
|
die.If(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func dumpHex(in []byte) string {
|
||||||
|
var s string
|
||||||
|
for i := range in {
|
||||||
|
s += fmt.Sprintf("%02X:", in[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Trim(s, ":")
|
||||||
|
}
|
||||||
|
|
||||||
|
type subjectPublicKeyInfo struct {
|
||||||
|
Algorithm pkix.AlgorithmIdentifier
|
||||||
|
SubjectPublicKey asn1.BitString
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var help, shouldMatch bool
|
||||||
|
flag.BoolVar(&help, "h", false, "print a help message and exit")
|
||||||
|
flag.BoolVar(&shouldMatch, "m", false, "all SKIs should match")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if help {
|
||||||
|
usage(os.Stdout)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ski string
|
||||||
|
for _, path := range flag.Args() {
|
||||||
|
public, kt, ft := parse(path)
|
||||||
|
|
||||||
|
var subPKI subjectPublicKeyInfo
|
||||||
|
_, err := asn1.Unmarshal(public, &subPKI)
|
||||||
|
if err != nil {
|
||||||
|
lib.Warn(err, "failed to get subject PKI")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pubHash := sha1.Sum(subPKI.SubjectPublicKey.Bytes)
|
||||||
|
pubHashString := dumpHex(pubHash[:])
|
||||||
|
if ski == "" {
|
||||||
|
ski = pubHashString
|
||||||
|
}
|
||||||
|
|
||||||
|
if shouldMatch && ski != pubHashString {
|
||||||
|
lib.Warnx("%s: SKI mismatch (%s != %s)",
|
||||||
|
path, ski, pubHashString)
|
||||||
|
}
|
||||||
|
fmt.Printf("%s %s (%s %s)\n", path, pubHashString, kt, ft)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue