Skip to content

Commit

Permalink
Merge pull request #138 from suizman/gen_certs
Browse files Browse the repository at this point in the history
Add the ability to generate Tls certificate with QED command tool
  • Loading branch information
suizman committed Jun 20, 2019
2 parents 3971e9d + bd8059f commit f0aadc1
Show file tree
Hide file tree
Showing 15 changed files with 672 additions and 213 deletions.
6 changes: 5 additions & 1 deletion cmd/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,16 @@ import (

type GenerateConfig struct {
// Path to the private key file used to sign snapshots.
Path string
Path string `desc:"Set custom output directory"`

// DNSName or IPAddr for which the certificates will be generated.
Host string `desc:"Set custom DNS name or IP address for new certificates"`
}

func GenerateDefaultConfig() *GenerateConfig {
return &GenerateConfig{
Path: "/var/tmp",
Host: "localhost",
}
}

Expand Down
52 changes: 52 additions & 0 deletions cmd/generate_certs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
Copyright 2018-2019 Banco Bilbao Vizcaya Argentaria, S.A.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package cmd

import (
"fmt"

"github.com/bbva/qed/crypto"
"github.com/spf13/cobra"
)

var generateSelfSignedCert *cobra.Command = &cobra.Command{
Use: "self-signed-cert",
Short: "Generate Self Signed Certificates",
RunE: runSelfSignedCert,
}

func init() {
generateCmd.AddCommand(generateSelfSignedCert)
}

func runSelfSignedCert(cmd *cobra.Command, args []string) error {
var err error
conf := generateCtx.Value(k("generate.config")).(*GenerateConfig)

err = isValidFQDN(conf.Host)
if err != nil {
return fmt.Errorf("Invalid FQDN: %v", err)
}

cert, key, err := crypto.NewSelfSignedCert(conf.Path, conf.Host)
if err != nil {
return fmt.Errorf("Error: %v", err)
}
fmt.Printf("New Self Signed Certtifiates generated at:\n%v\n%v\n", cert, key)

return nil
}
52 changes: 52 additions & 0 deletions cmd/generate_csr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
Copyright 2018-2019 Banco Bilbao Vizcaya Argentaria, S.A.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package cmd

import (
"fmt"

"github.com/bbva/qed/crypto"
"github.com/spf13/cobra"
)

var generateCsrRequest *cobra.Command = &cobra.Command{
Use: "csr",
Short: "Generate Certificate Signing Request",
RunE: runCsr,
}

func init() {
generateCmd.AddCommand(generateCsrRequest)
}

func runCsr(cmd *cobra.Command, args []string) error {
var err error
conf := generateCtx.Value(k("generate.config")).(*GenerateConfig)

err = isValidFQDN(conf.Host)
if err != nil {
return fmt.Errorf("Invalid FQDN: %v", err)
}

cert, key, err := crypto.NewCsrRequest(conf.Path, conf.Host)
if err != nil {
return fmt.Errorf("Error: %v", err)
}
fmt.Printf("New Certificate Signing Request and Private Key generated at:\n%v\n%v\n", cert, key)

return nil
}
17 changes: 13 additions & 4 deletions cmd/generate_signerkeys.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,27 @@ import (
var generateSignerKeys *cobra.Command = &cobra.Command{
Use: "signerkeys",
Short: "Generate Signer Keys",
Run: runGenerateSignerKeys,
RunE: runGenerateSignerKeys,
}

func init() {
generateCmd.AddCommand(generateSignerKeys)
}

func runGenerateSignerKeys(cmd *cobra.Command, args []string) {

func runGenerateSignerKeys(cmd *cobra.Command, args []string) error {
var err error
conf := generateCtx.Value(k("generate.config")).(*GenerateConfig)

pubKey, priKey, _ := crypto.NewEd25519SignerKeysFile(conf.Path)
err = isValidFQDN(conf.Host)
if err != nil {
return fmt.Errorf("%v", err)
}

pubKey, priKey, err := crypto.NewEd25519SignerKeysFile(conf.Path)
if err != nil {
return err
}
fmt.Printf("New signer keys generated at:\n%v\n%v\n", pubKey, priKey)

return nil
}
131 changes: 131 additions & 0 deletions cmd/parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"
"net/url"
"strings"
"unicode"
)

const (
Expand All @@ -28,8 +29,138 @@ const (
errMissingURLHost = "missing URL Host"
errMissingURLPort = "missing URL Port"
errUnexpectedScheme = "unexpected URL Scheme"
errFQDN = "not a valid FQDN"
errFQDNInvalid = "only letters(a-z|A-Z), digits(0-9) and hyphens('-') for labels"
errFQDNTrailing = "trailing dot or hyphens are not allowed"
errFQDNLen = "max FQDN length is 253"
errFQDNLabelLen = "max FQDN length is 63"
errFQDNEmptyDot = "start or end with dots is not allowed"
errFQDNHyp = "labels cannot start or end with hyphens"
errTLDLen = "TLD length is not valid"
errTLDLet = "TLD allow only letters"
)

/*
Hostname FQDN validation
Hostnames are composed of a series of labels concatenated with dots.
Each label is 1 to 63 characters long, and may contain: the ASCII letters
a-z and A-Z, the digits 0-9, and the hyphen ('-'). Additionally: labels
cannot start or end with hyphens (RFC 952) labels can start with
numbers (RFC 1123) trailing dot is not allowed max length of ascii
hostname including dots is 253 characters TLD (last label) is at least 2
characters and only ASCII letters we want at least 1 level above TLD
Source: https://stackoverflow.com/questions/11809631/fully-qualified-domain-
name-validation, answer from JdeBP
<domain> ::= <subdomain> | " "
<subdomain> ::= <label> | <subdomain> "." <label>
<label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
<ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
<let-dig-hyp> ::= <let-dig> | "-"
<let-dig> ::= <letter> | <digit>
<letter> ::= any one of the 52 alphabetic characters A through Z in
upper case and a through z in lower case
<digit> ::= any one of the ten digits 0 through 9
*/

func isHyphen(s rune) bool {
return s == '-'
}

func isLet(s []rune) bool {
for l := range s {
return unicode.IsLetter(s[l])
}
return true
}

func isLetDig(s rune) bool {
return unicode.IsLetter(s) || unicode.IsDigit(s)
}

func isLetDigHyp(s rune) bool {
return isLetDig(s) || isHyphen(s)
}

func trailingDotHyp(s string) bool {
return strings.Contains(s, "..") || strings.Contains(s, "--")
}

func isLabel(label []rune) bool {

for _, s := range label {
if !isLetDigHyp(rune(s)) {
return false
}
}

if !isLetDigHyp(label[0]) {
return false
}

return true
}

func isSubDomain(names []string) bool {
for _, s := range names {
if len(s) < 1 || !isLabel([]rune(s)) {
return false
}
}
return true
}

func isValidFQDN(fqdn string) error {

components := strings.Split(fqdn, ".")
label := []rune(components[len(components)-1])

// Check TLD length.
if 1 == len(label) {
return fmt.Errorf("%s in %s", errTLDLen, fqdn)
}

// Check if TLD valid. Only letters ar allowed.
if !isLet(label) {
return fmt.Errorf("%s in %s", errTLDLet, fqdn)
}

// Check for invalid trailings.
if trailingDotHyp(fqdn) {
return fmt.Errorf("%s in %s", errFQDNTrailing, fqdn)
}

// Check if FQDN have valid length.
if len(fqdn) > 253 {
return fmt.Errorf("%s in %s", errFQDNLen, fqdn)
}

for c := range components {
l := components[c]

// Check if label have valid length.
if len(l) > 63 {
return fmt.Errorf("%s in %s", errFQDNLabelLen, fqdn)
}

// Check for empty values in components. Eg: acme.net.
if l == "" {
return fmt.Errorf("%s in %s", errFQDNEmptyDot, fqdn)
}

// Check for hyphens at begining|end.
if isHyphen(rune(l[0])) || isHyphen(rune(l[(len(l)-1)])) {
return fmt.Errorf("%s in %s", errFQDNHyp, fqdn)
}
}

if !isSubDomain(components) {
return fmt.Errorf("%s in %s", errFQDNInvalid, fqdn)
}

return nil
}

// urlParse function checks that given string parameters are valid URLs for
// REST requests: schema + hostname [ + port ]
func urlParse(endpoints ...string) error {
Expand Down
76 changes: 76 additions & 0 deletions cmd/parameters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,79 @@ func TestUrlParseNoSchemaRequired(t *testing.T) {
}
}
}

func TestIsValidFQDN(t *testing.T) {

testCases := []struct {
fqdn []string
expectedError string
}{
{
fqdn: []string{
"a..bc",
"xn--d1aacihrobi6i.xn--p1ai",
},
expectedError: errFQDNTrailing,
},
{
fqdn: []string{
"a.b",
},
expectedError: errTLDLen,
},
{
fqdn: []string{
"ec2-35-160-210-253.us-west-2-.compute.amazonaws.com",
"-ec2-35-160-210-253.us-west-2-.compute.amazonaws.com",
},
expectedError: errFQDNHyp,
},
{
fqdn: []string{
"ec2_35$160%210-253.us-west-2.compute.amazonaws.com",
"ec235160210-253.us-west-2.compute.@mazonaws.com",
},
expectedError: errFQDNInvalid,
},
{
fqdn: []string{
"ec2-35-160-210-253.us-west-2.compute.amazonaws.com.",
".ec2-35-160-210-253.us-west-2.compute.amazonaws.com",
".ec2-35-160-210-253.us-west-2.compute.amazonaws.com.",
"acme.net.",
},
expectedError: errFQDNEmptyDot,
},
{
fqdn: []string{
"label.name.321",
"so-me.na-me.567",
},
expectedError: errTLDLet,
},
{
fqdn: []string{
"a23456789-123456789-123456789-123456789-123456789-123456789-1234.b23.com",
"b23.a23456789-123456789-123456789-123456789-123456789-123456789-1234.com",
},
expectedError: errFQDNLabelLen,
},
{
fqdn: []string{
"a23456789-a23456789-a234567890.a23456789.a23456789.a23456789.a23456789.a23456789.a23456789.a23456789.a23456789.a23456789.a23456789.a23456789.a23456789.a23456789.a23456789.a23456789.a23456789.a23456789.a23456789.a23456789.a23456789.a23456789.a2345678.com.",
},
expectedError: errFQDNLen,
},
}

for _, c := range testCases {
for _, e := range c.fqdn {
err := isValidFQDN(e)
if c.expectedError == "" {
require.NoError(t, err, "Unexpected error")
continue
}
require.Equal(t, err, fmt.Errorf("%s in %s", c.expectedError, e), "Errors do not match")
}
}
}

0 comments on commit f0aadc1

Please sign in to comment.