|
| 1 | +package main |
| 2 | + |
| 3 | +import ( |
| 4 | + "crypto/ecdsa" |
| 5 | + "crypto/elliptic" |
| 6 | + "crypto/rand" |
| 7 | + "crypto/rsa" |
| 8 | + "crypto/x509" |
| 9 | + "crypto/x509/pkix" |
| 10 | + "encoding/asn1" |
| 11 | + "encoding/pem" |
| 12 | + "fmt" |
| 13 | + "math/big" |
| 14 | + "time" |
| 15 | +) |
| 16 | + |
| 17 | +// KeyType for RSA/ECDSA |
| 18 | +type KeyType uint8 |
| 19 | + |
| 20 | +// CertConfig struct for |
| 21 | +// generating base certificates x509 |
| 22 | +// for ssl/tls auth |
| 23 | +type CertConfig struct { |
| 24 | + // Subject identifies the entity associated with the public |
| 25 | + // key stored in the subject public key fields. |
| 26 | + Subject pkix.Name |
| 27 | + // ValidFrom creation date formated as Feb 16 10:04:20 2016 |
| 28 | + ValidFrom time.Time |
| 29 | + // ValidFor the duration that certificate will be valid for |
| 30 | + // The validity period for a certificate is the period of time |
| 31 | + // form notBefore through notAfter, inclusive. |
| 32 | + // Note this fileds will be parsed into the notAfter,notBefore |
| 33 | + // x509 cert fields |
| 34 | + ValidFor time.Duration |
| 35 | + // SizeT can represent the size of bytes RSA cert if you specify in the |
| 36 | + SizeT int |
| 37 | + // Method to be RSA or it can be ECDSA flag if you specify the ECDSA flag |
| 38 | + Method KeyType |
| 39 | +} |
| 40 | + |
| 41 | +// definition flags for specific elliptic curves formats |
| 42 | +const ( |
| 43 | + // P224 curve which implements P-224 (see FIPS 186-3, section D.2.2) |
| 44 | + P224 = iota |
| 45 | + // P256 curve which implements P-256 (see FIPS 186-3, section D.2.3) |
| 46 | + P256 |
| 47 | + // P384 curve which implements P-384 (see FIPS 186-3, section D.2.4) |
| 48 | + P384 |
| 49 | + // P521 curve which implements P-521 (see FIPS 186-3, section D.2.5) |
| 50 | + P521 |
| 51 | + |
| 52 | + // definition flags for specific methods that the cert will be generated |
| 53 | + // RSA method |
| 54 | + RSA KeyType = iota |
| 55 | + // ECDSA method |
| 56 | + ECDSA |
| 57 | +) |
| 58 | + |
| 59 | +// OtherName type for asn1 encoding |
| 60 | +type OtherName struct { |
| 61 | + A string `asn1:"utf8"` |
| 62 | +} |
| 63 | + |
| 64 | +// GeneralName type for asn1 encoding |
| 65 | +type GeneralName struct { |
| 66 | + OID asn1.ObjectIdentifier |
| 67 | + OtherName `asn1:"tag:0"` |
| 68 | +} |
| 69 | + |
| 70 | +// GeneralNames type for asn1 encoding |
| 71 | +type GeneralNames struct { |
| 72 | + GeneralName `asn1:"tag:0"` |
| 73 | +} |
| 74 | + |
| 75 | +var ( |
| 76 | + // https://support.microsoft.com/en-us/kb/287547 |
| 77 | + // szOID_NT_PRINCIPAL_NAME 1.3.6.1.4.1.311.20.2.3 |
| 78 | + szOID = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 20, 2, 3} |
| 79 | + // http://www.umich.edu/~x509/ssleay/asn1-oids.html |
| 80 | + // 2 5 29 17 subjectAltName |
| 81 | + subjAltName = asn1.ObjectIdentifier{2, 5, 29, 17} |
| 82 | +) |
| 83 | + |
| 84 | +// getUPNExtensionValue returns marsheled asn1 encoded info |
| 85 | +func getUPNExtensionValue(subject pkix.Name) ([]byte, error) { |
| 86 | + // returns the ASN.1 encoding of val |
| 87 | + // in addition to the struct tags recognized |
| 88 | + // we used: |
| 89 | + // utf8 => causes string to be marsheled as ASN.1, UTF8 strings |
| 90 | + // tag:x => specifies the ASN.1 tag number; imples ASN.1 CONTEXT SPECIFIC |
| 91 | + return asn1.Marshal(GeneralNames{ |
| 92 | + GeneralName: GeneralName{ |
| 93 | + // init our ASN.1 object identifier |
| 94 | + OID: szOID, |
| 95 | + OtherName: OtherName{ |
| 96 | + A: subject.CommonName, |
| 97 | + }, |
| 98 | + }, |
| 99 | + }) |
| 100 | +} |
| 101 | + |
| 102 | +// NewCert generates a new x509 certificates based on the CertConfig passed |
| 103 | +func NewCert(config CertConfig) (string, string, error) { |
| 104 | + var ( |
| 105 | + err error |
| 106 | + notAfter time.Time |
| 107 | + notBefore time.Time |
| 108 | + // PrivateKey that can be: |
| 109 | + // type e.g *rsa.PrivateKey |
| 110 | + // or *ecdsa.PrivateKey |
| 111 | + PrivateKey interface{} |
| 112 | + ) |
| 113 | + // parse the time vars from the config into notAfter and notBefore |
| 114 | + notBefore = config.ValidFrom |
| 115 | + notAfter = notBefore.Add(config.ValidFor) |
| 116 | + |
| 117 | + // choose the method to generate the certificate |
| 118 | + switch config.Method { |
| 119 | + case RSA: |
| 120 | + PrivateKey, err = rsa.GenerateKey(rand.Reader, config.SizeT) |
| 121 | + if err != nil { |
| 122 | + return "", "", fmt.Errorf("Can't generate rsa private key") |
| 123 | + } |
| 124 | + |
| 125 | + // if the ECDSA byte is set |
| 126 | + case ECDSA: |
| 127 | + PrivateKey, err = genKeyEcdsa(config.SizeT) |
| 128 | + if err != nil { |
| 129 | + return "", "", fmt.Errorf("Can't generate ecdsa private key") |
| 130 | + } |
| 131 | + default: |
| 132 | + return "", "", fmt.Errorf("Unknown method type to use") |
| 133 | + } |
| 134 | + |
| 135 | + // get asn1 encoded info of the subject pkix |
| 136 | + value, err := getUPNExtensionValue(config.Subject) |
| 137 | + if err != nil { |
| 138 | + return "", "", fmt.Errorf("Can't marshal asn1 encoded") |
| 139 | + } |
| 140 | + |
| 141 | + // A serial number can be up to 20 octets in size. |
| 142 | + // https://tools.ietf.org/html/rfc5280#section-4.1.2.2 |
| 143 | + serialNum, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 8*20)) |
| 144 | + if err != nil { |
| 145 | + return "", "", fmt.Errorf("Failed to generate serial number: %s", err.Error()) |
| 146 | + } |
| 147 | + |
| 148 | + // make a new x509 temaplte certificate |
| 149 | + // entry point for the generation process |
| 150 | + template := x509.Certificate{ |
| 151 | + SerialNumber: serialNum, |
| 152 | + Subject: config.Subject, |
| 153 | + // when extenstions are used, as expected in this profile, |
| 154 | + // versions MUST be 3(value is 2). |
| 155 | + Version: 2, |
| 156 | + NotBefore: config.ValidFrom, |
| 157 | + NotAfter: notAfter, |
| 158 | + KeyUsage: x509.KeyUsageKeyEncipherment, |
| 159 | + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, |
| 160 | + ExtraExtensions: []pkix.Extension{ |
| 161 | + { |
| 162 | + Id: subjAltName, |
| 163 | + Critical: false, |
| 164 | + Value: value, |
| 165 | + }, |
| 166 | + }, |
| 167 | + } |
| 168 | + |
| 169 | + cert, err := x509.CreateCertificate(rand.Reader, &template, &template, getPublicKey(PrivateKey), PrivateKey) |
| 170 | + if err != nil { |
| 171 | + return "", "", fmt.Errorf("Failed to generate cert") |
| 172 | + } |
| 173 | + |
| 174 | + certPem := pem.EncodeToMemory(&pem.Block{ |
| 175 | + Type: "CERTIFICATE", |
| 176 | + Bytes: cert, |
| 177 | + }) |
| 178 | + |
| 179 | + privPem, err := exportPrivKeyToPem(PrivateKey) |
| 180 | + if err != nil { |
| 181 | + return "", "", fmt.Errorf("Failed to export priv key") |
| 182 | + } |
| 183 | + |
| 184 | + return string(certPem), string(privPem), nil |
| 185 | +} |
| 186 | + |
| 187 | +// getPublicKey fetch public key with assertion |
| 188 | +func getPublicKey(p interface{}) interface{} { |
| 189 | + switch t := p.(type) { |
| 190 | + case *rsa.PrivateKey: |
| 191 | + return t.Public() |
| 192 | + case *ecdsa.PrivateKey: |
| 193 | + return t.Public() |
| 194 | + default: |
| 195 | + return nil |
| 196 | + } |
| 197 | +} |
| 198 | + |
| 199 | +// choose the format for the key and generate it |
| 200 | +func genKeyEcdsa(szT int) (*ecdsa.PrivateKey, error) { |
| 201 | + switch szT { |
| 202 | + case P224: |
| 203 | + return ecdsa.GenerateKey(elliptic.P224(), rand.Reader) |
| 204 | + case P256: |
| 205 | + return ecdsa.GenerateKey(elliptic.P256(), rand.Reader) |
| 206 | + case P384: |
| 207 | + return ecdsa.GenerateKey(elliptic.P384(), rand.Reader) |
| 208 | + case P521: |
| 209 | + return ecdsa.GenerateKey(elliptic.P521(), rand.Reader) |
| 210 | + default: |
| 211 | + return nil, fmt.Errorf("Can't generate private key, please specify a format") |
| 212 | + } |
| 213 | + |
| 214 | +} |
| 215 | + |
| 216 | +// ExportPrivKeyToPem exports private key from the previous generation |
| 217 | +func exportPrivKeyToPem(key interface{}) ([]byte, error) { |
| 218 | + |
| 219 | + var pemBlock *pem.Block |
| 220 | + |
| 221 | + switch k := key.(type) { |
| 222 | + case *rsa.PrivateKey: |
| 223 | + pemBlock = &pem.Block{ |
| 224 | + Type: "RSA PRIVATE KEY", |
| 225 | + Bytes: x509.MarshalPKCS1PrivateKey(k), |
| 226 | + } |
| 227 | + case *ecdsa.PrivateKey: |
| 228 | + b, err := x509.MarshalECPrivateKey(k) |
| 229 | + if err != nil { |
| 230 | + return nil, fmt.Errorf("Unable to export ecdsa private key") |
| 231 | + } |
| 232 | + pemBlock = &pem.Block{ |
| 233 | + Type: "EC PRIVATE KEY", |
| 234 | + Bytes: b, |
| 235 | + } |
| 236 | + default: |
| 237 | + return nil, fmt.Errorf("The private key is not RSA or ECDSA") |
| 238 | + } |
| 239 | + |
| 240 | + return pem.EncodeToMemory(pemBlock), nil |
| 241 | +} |
0 commit comments