Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move certificate parameters to a dedicated struct #54

Merged
merged 5 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
"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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@

return csr, nil
}

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

Check warning on line 252 in pkg/pivit/generate.go

View check run for this annotation

Codecov / codecov/patch

pkg/pivit/generate.go#L251-L252

Added lines #L251 - L252 were not covered by tests
}
}
return false
}
Loading