diff --git a/ca.go b/ca.go index 767ad51..f36e831 100644 --- a/ca.go +++ b/ca.go @@ -7,6 +7,7 @@ import ( "encoding/pem" "errors" "io/fs" + "net" "os" "path/filepath" "time" @@ -32,6 +33,7 @@ type Identity struct { Province string `json:"province" example:"Veldhoven"` // Province name EmailAddresses string `json:"email" example:"sec@company.com"` // Email Address DNSNames []string `json:"dns_names" example:"ca.example.com,root-ca.example.com"` // DNS Names list + IPAddresses []net.IP `json:"ip_addresses,omitempty" example:"127.0.0.1,192.168.0.1"` // IP Address list Intermediate bool `json:"intermediate" example:"false"` // Intermendiate Certificate Authority (default is false) KeyBitSize int `json:"key_size" example:"2048"` // Key Bit Size (defaul: 2048) Valid int `json:"valid" example:"365"` // Minimum 1 day, maximum 825 days -- Default: 397 @@ -138,6 +140,7 @@ func (c *CA) create(commonName, parentCommonName string, id Identity) error { id.EmailAddresses, id.Valid, id.DNSNames, + id.IPAddresses, privKey, pubKey, storage.CreationTypeCA, @@ -167,6 +170,7 @@ func (c *CA) create(commonName, parentCommonName string, id Identity) error { id.EmailAddresses, id.Valid, id.DNSNames, + id.IPAddresses, privKey, parentPrivateKey, parentCertificate, @@ -367,7 +371,7 @@ func (c *CA) issueCertificate(commonName string, id Identity) (certificate Certi certificate.publicKey = *pubKey certificate.PublicKey = string(publicKeyString) - csrBytes, err := cert.CreateCSR(c.CommonName, commonName, id.Country, id.Province, id.Locality, id.Organization, id.OrganizationalUnit, id.EmailAddresses, id.DNSNames, privKey, storage.CreationTypeCertificate) + csrBytes, err := cert.CreateCSR(c.CommonName, commonName, id.Country, id.Province, id.Locality, id.Organization, id.OrganizationalUnit, id.EmailAddresses, id.DNSNames, id.IPAddresses, privKey, storage.CreationTypeCertificate) if err != nil { return certificate, err } diff --git a/cert/cert.go b/cert/cert.go index 03b0caa..2c23f3d 100644 --- a/cert/cert.go +++ b/cert/cert.go @@ -39,6 +39,7 @@ import ( "encoding/pem" "errors" "math/big" + "net" "path/filepath" "time" @@ -72,7 +73,7 @@ func newSerialNumber() (serialNumber *big.Int) { // CreateCSR creates a Certificate Signing Request returning certData with CSR. // // The CSR is also stored in $CAPATH with extension .csr -func CreateCSR(CACommonName, commonName, country, province, locality, organization, organizationalUnit, emailAddresses string, dnsNames []string, priv *rsa.PrivateKey, creationType storage.CreationType) (csr []byte, err error) { +func CreateCSR(CACommonName, commonName, country, province, locality, organization, organizationalUnit, emailAddresses string, dnsNames []string, ipAddresses []net.IP, priv *rsa.PrivateKey, creationType storage.CreationType) (csr []byte, err error) { var oidEmailAddress = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1} subject := pkix.Name{ @@ -93,6 +94,7 @@ func CreateCSR(CACommonName, commonName, country, province, locality, organizati RawSubject: asn1Subj, EmailAddresses: []string{emailAddresses}, SignatureAlgorithm: x509.SHA256WithRSA, + IPAddresses: ipAddresses, } dnsNames = append(dnsNames, commonName) @@ -184,6 +186,7 @@ func CreateRootCert( emailAddresses string, valid int, dnsNames []string, + ipAddresses []net.IP, privateKey *rsa.PrivateKey, publicKey *rsa.PublicKey, creationType storage.CreationType, @@ -199,6 +202,7 @@ func CreateRootCert( emailAddresses, valid, dnsNames, + ipAddresses, privateKey, nil, // parentPrivateKey nil, // parentCertificate @@ -223,6 +227,7 @@ func CreateCACert( emailAddresses string, validDays int, dnsNames []string, + ipAddresses []net.IP, privateKey, parentPrivateKey *rsa.PrivateKey, parentCertificate *x509.Certificate, @@ -250,6 +255,7 @@ func CreateCACert( ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign | x509.KeyUsageCRLSign, BasicConstraintsValid: true, + IPAddresses: ipAddresses, } dnsNames = append(dnsNames, commonName) caCert.DNSNames = dnsNames @@ -346,7 +352,8 @@ func CASignCSR(CACommonName string, csr x509.CertificateRequest, caCert *x509.Ce NotBefore: time.Now(), NotAfter: time.Now().AddDate(0, 0, valid), KeyUsage: x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + IPAddresses: csr.IPAddresses, } csrTemplate.DNSNames = csr.DNSNames diff --git a/goca_test.go b/goca_test.go index bf40ce5..a8e273c 100644 --- a/goca_test.go +++ b/goca_test.go @@ -2,8 +2,10 @@ package goca import ( "fmt" + "net" "os" "path/filepath" + "slices" "testing" ) @@ -106,6 +108,7 @@ func TestFunctionalRootCAIssueNewCertificate(t *testing.T) { Province: "Veldhoven", Intermediate: true, DNSNames: []string{"w3.intranet.go-root.ca"}, + IPAddresses: []net.IP{net.IPv4(192, 168, 17, 243)}, } RootCA, err := Load("go-root.ca") @@ -135,6 +138,16 @@ func TestFunctionalRootCAIssueNewCertificate(t *testing.T) { if fi.Mode() != GoodKeyPerms { t.Errorf("Expected key.pem permissions " + fmt.Sprint(GoodKeyPerms) + " but got: " + fmt.Sprint(fi.Mode())) } + + if !slices.Contains(intranetCert.certificate.DNSNames, intranteIdentity.DNSNames[0]) { + t.Errorf("Expected issued certificate to have DNS SAN %q but got: %q", intranteIdentity.DNSNames[0], intranetCert.certificate.DNSNames) + } + + if !slices.ContainsFunc(intranetCert.certificate.IPAddresses, func(ip net.IP) bool { + return ip.Equal(intranteIdentity.IPAddresses[0]) + }) { + t.Errorf("Expected issued certificate to have IP SAN %q but got: %q", intranteIdentity.IPAddresses[0], intranetCert.certificate.IPAddresses) + } } func TestFunctionalRootCALoadCertificates(t *testing.T) {