Skip to content

Commit

Permalink
Merge pull request #54 from cashapp/move-certificate-params-to-struct
Browse files Browse the repository at this point in the history
Move certificate parameters to a dedicated struct
  • Loading branch information
yoavamit authored Aug 16, 2024
2 parents 51d77d0 + c616c6a commit 3b5a1af
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 45 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ test: pivit
set -e ;\
go test -coverprofile=cover.out ./pkg/... ;\
file pivit ;\
./pivit --help ;\
./pivit --help 2> /dev/null ;\
)

#
Expand Down
44 changes: 35 additions & 9 deletions cmd/pivit/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package main
import (
"fmt"
"io"
"net"
"net/url"
"os"
"strings"

"github.com/cashapp/pivit/pkg/pivit"
"github.com/go-piv/piv-go/piv"
Expand Down Expand Up @@ -219,16 +222,39 @@ func runCommand() error {
if err != nil {
return err
}

certEmailAddress := os.Getenv("PIVIT_EMAIL")
certOrg := strings.Split(os.Getenv("PIVIT_ORG"), ",")
certOrgUnit := strings.Split(os.Getenv("PIVIT_ORG_UNIT"), ",")
certURIs := strings.Split(os.Getenv("PIVIT_CERT_URIS"), " ")
certURLs := make([]*url.URL, 0)
for _, uri := range certURIs {
url, err := url.Parse(uri)
if err != nil {
certURLs = append(certURLs, url)
}
}

certParams := pivit.CertificateParameters{
SubjectEmailAddress: certEmailAddress,
SubjectOrganization: certOrg,
SubjectOrganizationUnit: certOrgUnit,
CertificateURIs: certURLs,
CertificateIPAddresses: []net.IP{},
CertificateEmailAddresses: []string{certEmailAddress},
CertificateDNSNames: []string{},
}
opts := &pivit.GenerateCertificateOpts{
Algorithm: algorithm,
SelfSign: *selfSignFlag,
GenerateCsr: generateCsr,
AssumeYes: *assumeYesFlag,
PINPolicy: pinPolicy,
TouchPolicy: touchPolicy,
Slot: pivit.GetSlot(*slot),
Prompt: os.Stdin,
Pin: pin,
Algorithm: algorithm,
SelfSign: *selfSignFlag,
GenerateCsr: generateCsr,
AssumeYes: *assumeYesFlag,
PINPolicy: pinPolicy,
TouchPolicy: touchPolicy,
CertificateParameters: certParams,
Slot: pivit.GetSlot(*slot),
Prompt: os.Stdin,
Pin: pin,
}
if generateCsr {
fmt.Println("Touch Yubikey now to sign your CSR...")
Expand Down
85 changes: 50 additions & 35 deletions pkg/pivit/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import (
"math/big"
"net"
"net/url"
"os"
"strconv"
"strings"

"github.com/go-piv/piv-go/piv"
"github.com/pkg/errors"
Expand All @@ -33,6 +31,9 @@ type GenerateCertificateOpts struct {
PINPolicy piv.PINPolicy
// TouchPolicy specifies when (or if) to touch the yubikey to access the private key
TouchPolicy piv.TouchPolicy
// CertificateParameters contains the subject information and DNS names, URIs, IP addresses,
// and email addresses the generated certificate/CSR will be tied to
CertificateParameters CertificateParameters
// Slot to use for the private key
Slot piv.Slot
// Prompt where to get user confirmation from
Expand All @@ -41,6 +42,19 @@ type GenerateCertificateOpts struct {
Pin string
}

// CertificateParameters specifies the information encoded in the certificate
// it's used to construct the certificate's subject, and targets (IP addresses, email, URIs, and DNS names)
type CertificateParameters struct {
SubjectEmailAddress string
SubjectOrganization []string
SubjectOrganizationUnit []string

CertificateURIs []*url.URL
CertificateIPAddresses []net.IP
CertificateEmailAddresses []string
CertificateDNSNames []string
}

type GenerateCertificateResults struct {
// AttestationCertificate PEM encoded X509 certificate that identifies the specific Yubikey device.
// This certificate is signed by Yubico Inc.'s CA.
Expand Down Expand Up @@ -117,13 +131,14 @@ func GenerateCertificate(yk Pivit, opts *GenerateCertificateOpts) (*GenerateCert
if err != nil {
return nil, errors.Wrap(err, "verify device certificate")
}
deviceSerialNumber := strconv.FormatUint(uint64(attestation.Serial), 10)
result.Certificate = pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: keyCert.Raw,
})

if opts.SelfSign {
certificate, err := selfCertificate(strconv.FormatUint(uint64(attestation.Serial), 10), publicKey, privateKey)
certificate, err := selfCertificate(deviceSerialNumber, publicKey, privateKey, opts.CertificateParameters)
if err != nil {
return nil, err
}
Expand All @@ -142,7 +157,7 @@ func GenerateCertificate(yk Pivit, opts *GenerateCertificateOpts) (*GenerateCert
Bytes: certificate.Raw,
})
} else if opts.GenerateCsr {
certRequest, err := certificateRequest(strconv.FormatUint(uint64(attestation.Serial), 10), privateKey)
certRequest, err := certificateRequest(deviceSerialNumber, privateKey, opts.CertificateParameters)
if err != nil {
return nil, err
}
Expand All @@ -163,15 +178,12 @@ func randomSerial() (*big.Int, error) {
return n, err
}

func selfCertificate(serialNumber string, publicKey crypto.PublicKey, privateKey crypto.PrivateKey) (*x509.Certificate, error) {
emailAddress := os.Getenv("PIVIT_EMAIL")
pivitOrg := strings.Split(os.Getenv("PIVIT_ORG"), ",")
pivitOrgUnit := strings.Split(os.Getenv("PIVIT_ORG_UNIT"), ",")
func selfCertificate(serialNumber string, publicKey crypto.PublicKey, privateKey crypto.PrivateKey, params CertificateParameters) (*x509.Certificate, error) {
subject := pkix.Name{
Organization: pivitOrg,
OrganizationalUnit: pivitOrgUnit,
Organization: params.SubjectOrganization,
OrganizationalUnit: params.SubjectOrganizationUnit,
SerialNumber: serialNumber,
CommonName: emailAddress,
CommonName: params.SubjectEmailAddress,
}
extKeyUsage := []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth,
Expand All @@ -182,13 +194,17 @@ func selfCertificate(serialNumber string, publicKey crypto.PublicKey, privateKey
return nil, errors.Wrap(err, "create certificate random serial")
}

if !contains(params.CertificateEmailAddresses, params.SubjectEmailAddress) {
params.CertificateEmailAddresses = append(params.CertificateEmailAddresses, params.SubjectEmailAddress)
}

cert := &x509.Certificate{
Subject: subject,
SerialNumber: serial,
DNSNames: []string{},
EmailAddresses: []string{emailAddress},
IPAddresses: []net.IP{},
URIs: []*url.URL{},
DNSNames: params.CertificateDNSNames,
EmailAddresses: params.CertificateEmailAddresses,
IPAddresses: params.CertificateIPAddresses,
URIs: params.CertificateURIs,
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: extKeyUsage,
ExtraExtensions: []pkix.Extension{},
Expand All @@ -202,33 +218,23 @@ func selfCertificate(serialNumber string, publicKey crypto.PublicKey, privateKey
return x509.ParseCertificate(data)
}

func certificateRequest(serialNumber string, privateKey crypto.PrivateKey) ([]byte, error) {
emailAddress := os.Getenv("PIVIT_EMAIL")
pivitOrg := strings.Split(os.Getenv("PIVIT_ORG"), ",")
pivitOrgUnit := strings.Split(os.Getenv("PIVIT_ORG_UNIT"), ",")
pivitCertUris := strings.Split(os.Getenv("PIVIT_CERT_URIS"), " ")

certUrls := make([]*url.URL, 0)
for _, urlString := range pivitCertUris {
parsed, err := url.Parse(urlString)
if err == nil {
certUrls = append(certUrls, parsed)
}
func certificateRequest(serialNumber string, privateKey crypto.PrivateKey, params CertificateParameters) ([]byte, error) {
if !contains(params.CertificateEmailAddresses, params.SubjectEmailAddress) {
params.CertificateEmailAddresses = append(params.CertificateEmailAddresses, params.SubjectEmailAddress)
}

subject := pkix.Name{
Organization: pivitOrg,
OrganizationalUnit: pivitOrgUnit,
Organization: params.SubjectOrganization,
OrganizationalUnit: params.SubjectOrganizationUnit,
SerialNumber: serialNumber,
CommonName: emailAddress,
CommonName: params.SubjectEmailAddress,
}
certRequest := &x509.CertificateRequest{
SignatureAlgorithm: x509.ECDSAWithSHA256,
Subject: subject,
DNSNames: []string{},
EmailAddresses: []string{emailAddress},
IPAddresses: []net.IP{},
URIs: certUrls,
DNSNames: params.CertificateDNSNames,
EmailAddresses: params.CertificateEmailAddresses,
IPAddresses: params.CertificateIPAddresses,
URIs: params.CertificateURIs,
ExtraExtensions: []pkix.Extension{},
}

Expand All @@ -239,3 +245,12 @@ func certificateRequest(serialNumber string, privateKey crypto.PrivateKey) ([]by

return csr, nil
}

func contains(slice []string, emailAddress string) bool {
for _, element := range slice {
if element == emailAddress {
return true
}
}
return false
}

0 comments on commit 3b5a1af

Please sign in to comment.