Skip to content

Instantly share code, notes, and snippets.

@gabriel-samfira
Last active August 14, 2023 08:50
Show Gist options
  • Save gabriel-samfira/fec197aeae67d661b5207dfc5440740a to your computer and use it in GitHub Desktop.
Save gabriel-samfira/fec197aeae67d661b5207dfc5440740a to your computer and use it in GitHub Desktop.
Generate a self signed x509 with Microsoft UPN extension
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"fmt"
"log"
"math/big"
"os"
"time"
)
// UPN type for asn1 encoding. This will hold
// our utf-8 encoded string.
type UPN struct {
A string `asn1:"utf8"`
}
// OtherName type for asn1 encoding
type OtherName struct {
OID asn1.ObjectIdentifier
Value interface{} `asn1:"tag:0"`
}
// GeneralNames type for asn1 encoding
type GeneralNames struct {
OtherName OtherName `asn1:"tag:0"`
}
func writeKeyFile(path string, key *ecdsa.PrivateKey) error {
keyOut, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return fmt.Errorf("failed to open %s for writing:", path, err)
}
b, err := x509.MarshalECPrivateKey(key)
if err != nil {
return err
}
if err := pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}); err != nil {
return fmt.Errorf("failed to write data to %s: %s", path, err)
}
if err := keyOut.Close(); err != nil {
return fmt.Errorf("error closing %s: %s", path, err)
}
return nil
}
func writeCertFile(path string, certBytes []byte) error {
certOut, err := os.Create(path)
if err != nil {
return fmt.Errorf("failed to open %s for writing: %s", path, err)
}
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes}); err != nil {
return fmt.Errorf("failed to write data to %s: %s", path, err)
}
if err := certOut.Close(); err != nil {
return fmt.Errorf("error closing %s: %s", path, err)
}
return nil
}
func main() {
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
log.Fatal(err)
}
now := time.Now()
notBefore := now.Add(time.Duration(-24) * time.Hour) // 1 day ago. To account for any time skew.
notAfter := notBefore.Add(8760 * time.Hour) // 1 year
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
log.Fatalf("failed to generate serial number: %s", err)
}
upnExt, err := asn1.Marshal(GeneralNames{
OtherName: OtherName{
// init our ASN.1 object identifier
OID: asn1.ObjectIdentifier{
1, 3, 6, 1, 4, 1, 311, 20, 2, 3},
Value: UPN{
A: "johnDoe@example.com",
},
},
})
if err != nil {
log.Fatal(err)
}
extSubjectAltName := pkix.Extension{
Id: asn1.ObjectIdentifier{2, 5, 29, 17},
Critical: false,
Value: upnExt,
}
// Define the x509 template
certificateTemplate := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: "johnDoe@example.com",
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
// Add subjectAltName
ExtraExtensions: []pkix.Extension{extSubjectAltName},
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
BasicConstraintsValid: true,
}
derBytes, err := x509.CreateCertificate(rand.Reader, &certificateTemplate, &certificateTemplate, &privateKey.PublicKey, privateKey)
if err != nil {
log.Fatal(err)
}
if err := writeKeyFile("key.pem", privateKey); err != nil {
log.Fatal(err)
}
if err := writeCertFile("cert.pem", derBytes); err != nil {
log.Fatal(err)
}
}
@gabriel-samfira
Copy link
Author

gabriel-samfira commented Aug 11, 2023 via email

@KarlRanseier
Copy link

@gabriel-samfira thanks for answering so fast !

yes - this (slightly modified code) is working perfectly on Windows:

certificateTemplate := &x509.Certificate{

. . .

UnknownExtKeyUsage: []asn1.ObjectIdentifier{
[]int{1, 3, 6, 1, 4, 1, 311, 20, 2, 2}, // SmartCard Logon
},
}

@gabriel-samfira
Copy link
Author

Awesome! Glad it worked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment