Skip to content

Instantly share code, notes, and snippets.

@bombsimon
Last active April 25, 2019 13:55
Show Gist options
  • Save bombsimon/2ae4fd40d4522ef14b4c0e8b7a57b587 to your computer and use it in GitHub Desktop.
Save bombsimon/2ae4fd40d4522ef14b4c0e8b7a57b587 to your computer and use it in GitHub Desktop.
Working with certificates in Go
package main
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"math/big"
"os"
"time"
)
func main() {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
panic(err)
}
csr, err := CreateCSR(key, "test-csr")
if err != nil {
panic(err)
}
pemEncodedCertificate, err := CreateCertificate(key)
if err != nil {
panic(err)
}
certificate, err := PEMBlockToCertificate(pemEncodedCertificate)
if err != nil {
panic(err)
}
fmt.Println("Your created Certificate Signing Request (CSR)")
fmt.Println(string(csr))
fmt.Println("")
fmt.Println("Your created PEM encoded certificate")
fmt.Println(string(pemEncodedCertificate))
fmt.Println("")
fmt.Println("Values from your certificate from converting PEM block")
fmt.Println("Subject", certificate.Subject)
fmt.Println("Issuer", certificate.Issuer)
fmt.Println("")
fmt.Println("Verify certificate (self signed, should not work)")
if err := VerifyCertificate(certificate); err != nil {
fmt.Println(err.Error())
fmt.Println("")
}
fmt.Println("Verify PEM encoded certificate (self signed, should not work)")
if err := VerifyCertificateFromPEM(pemEncodedCertificate); err != nil {
fmt.Println(err.Error())
fmt.Println("")
}
}
// CreateCSR will create a certificate signing request with the key passed.
func CreateCSR(key *rsa.PrivateKey, commonName string) ([]byte, error) {
csrSubject := pkix.Name{
CommonName: commonName,
Country: []string{"SE"},
Locality: []string{"Stockholm"},
Organization: []string{"Some Organization"},
OrganizationalUnit: []string{"Some Organization Unit"},
ExtraNames: []pkix.AttributeTypeAndValue{},
}
csrTemplate := x509.CertificateRequest{
Subject: csrSubject,
SignatureAlgorithm: x509.SHA256WithRSA,
}
csrRequest, err := x509.CreateCertificateRequest(rand.Reader, &csrTemplate, key)
if err != nil {
return nil, err
}
csrPEMBlock := pem.Block{
Type: "CERTIFICATE REQUEST",
Bytes: csrRequest,
}
buf := &bytes.Buffer{}
if err := pem.Encode(buf, &csrPEMBlock); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// CreateCertificate will create a new certificate and return it as a PEM
// encoded block.
func CreateCertificate(key *rsa.PrivateKey) ([]byte, error) {
cert := &x509.Certificate{
SerialNumber: big.NewInt(1653),
Subject: pkix.Name{
Country: []string{"SE"},
Locality: []string{"Stockholm"},
Organization: []string{"Some Organization"},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
IsCA: false,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}
certificate, err := x509.CreateCertificate(rand.Reader, cert, cert, &key.PublicKey, key)
if err != nil {
return nil, err
}
return CertificateToPEMBlock(&x509.Certificate{Raw: certificate})
}
// PEMBlockToCertificate will convert a PEM encoded block to a
// *x509.Certificate.
func PEMBlockToCertificate(bytes []byte) (*x509.Certificate, error) {
certs, err := PEMBlocksToCertificates(bytes)
if err != nil {
return nil, err
}
if len(certs) > 0 {
return certs[0], nil
}
return nil, nil
}
// PEMBlocksToCertificates will convert PEM encoded blocks to a slice of
// *x509.Certificate.
func PEMBlocksToCertificates(bytes []byte) ([]*x509.Certificate, error) {
if len(bytes) == 0 {
return []*x509.Certificate{}, nil
}
if bytes[0] == 0x30 {
// BER-/DER-encoded X.509 certificates always start with 0x30, and
// PEM-encoded ones never do, so this is a good guess.
return x509.ParseCertificates(bytes)
}
// Probably PEM
return PEMToCertificates(string(bytes))
}
// PEMToCertificates parses all certificates from PEM.
func PEMToCertificates(pemBlocks string) ([]*x509.Certificate, error) {
var (
bytes = []byte(pemBlocks)
certs []*x509.Certificate
)
if len(bytes) == 0 {
return nil, errors.New("empty PEM")
}
for len(bytes) > 0 {
var block *pem.Block
block, bytes = pem.Decode(bytes)
if block == nil {
return nil, errors.New("could not parse PEM")
}
if block.Type != "CERTIFICATE" {
continue
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, err
}
certs = append(certs, cert)
}
if len(certs) == 0 {
return nil, errors.New("no certificates in PEM")
}
return certs, nil
}
// CertificateToPEMBlock will convert a *x509.Certificate to a PEM encoded
// block.
func CertificateToPEMBlock(cert *x509.Certificate) ([]byte, error) {
if cert == nil {
return nil, fmt.Errorf("invalid (no) certificate provided")
}
buf := &bytes.Buffer{}
err := pem.Encode(buf, &pem.Block{
Type: "CERTIFICATE",
Bytes: cert.Raw,
})
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// VerifyCertificateFromPEM will check if a certificate is valid by passing a
// PEM encoded certificate block.
func VerifyCertificateFromPEM(cert []byte) error {
pemBlock, err := PEMBlockToCertificate(cert)
if err != nil {
return err
}
return VerifyCertificate(pemBlock)
}
// VerifyCertificate will check if a certificate is valid by passing a
// *x509.Certificate.
func VerifyCertificate(cert *x509.Certificate) error {
rootCertificates, err := x509.SystemCertPool()
if err != nil {
return err
}
verifyOptions := x509.VerifyOptions{
Roots: rootCertificates,
KeyUsages: []x509.ExtKeyUsage{
x509.ExtKeyUsageAny,
},
}
_, err = cert.Verify(verifyOptions)
if err != nil {
return err
}
return nil
}
// ReadCertificateFromFile will read a certificate from a file.
func ReadCertificateFromFile(file string) (*x509.Certificate, error) {
fileBytes, err := ioutil.ReadFile(file)
if err != nil {
return nil, err
}
block, _ := pem.Decode(fileBytes)
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, err
}
return cert, nil
}
// ReadRSAPublicKeyFromFile will read a *rsa.PublicKey from file.
func ReadRSAPublicKeyFromFile(file string) (*rsa.PublicKey, error) {
fileBytes, err := ioutil.ReadFile(file)
if err != nil {
return nil, err
}
block, _ := pem.Decode(fileBytes)
if block == nil {
return nil, fmt.Errorf("could not decode PEM blocK")
}
parsedKey, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
cert, cErr := x509.ParseCertificate(block.Bytes)
if cErr != nil {
return nil, fmt.Errorf("couldn't parse public key")
}
parsedKey = cert.PublicKey
}
pk, ok := parsedKey.(*rsa.PublicKey)
if !ok {
return nil, fmt.Errorf("could not read RSA public key")
}
return pk, nil
}
// SavePublicKey will save an *rsa.PublicKey to file in DER format.
func SavePublicKey(pk *rsa.PublicKey, filename string) error {
asn1Bytes, err := asn1.Marshal(*pk)
if err != nil {
return err
}
pemfile, err := os.Create(filename)
if err != nil {
return err
}
defer pemfile.Close()
err = pem.Encode(pemfile, &pem.Block{
Type: "PUBLIC KEY",
Bytes: asn1Bytes,
})
if err != nil {
return err
}
return nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment