Skip to content

Commit 43fe5d5

Browse files
committed
Add command to generate certificates
1 parent 00d04de commit 43fe5d5

File tree

2 files changed

+226
-1
lines changed

2 files changed

+226
-1
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
arduino-create-agent
1+
arduino-create-agent
2+
ca.key.pem
3+
ca.cert.pem
4+
key.pem
5+
cert.pem

cli/certificates/main.go

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
/*
2+
* This file is part of arduino-create-agent.
3+
*
4+
* arduino-create-agent is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation; either version 2 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program; if not, write to the Free Software
16+
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17+
*
18+
* As a special exception, you may use this file as part of a free software
19+
* library without restriction. Specifically, if other files instantiate
20+
* templates or use macros or inline functions from this file, or you compile
21+
* this file and link it with other files to produce an executable, this
22+
* file does not by itself cause the resulting executable to be covered by
23+
* the GNU General Public License. This exception does not however
24+
* invalidate any other reasons why the executable file might be covered by
25+
* the GNU General Public License.
26+
*
27+
* Copyright 2017 BCMI LABS SA (http://www.arduino.cc/)
28+
*/
29+
package main
30+
31+
import (
32+
"crypto/ecdsa"
33+
"crypto/elliptic"
34+
"crypto/rand"
35+
"crypto/rsa"
36+
"crypto/x509"
37+
"crypto/x509/pkix"
38+
"encoding/pem"
39+
"fmt"
40+
"log"
41+
"math/big"
42+
"net"
43+
"os"
44+
"strings"
45+
"time"
46+
)
47+
48+
var (
49+
host = "localhost"
50+
validFrom = ""
51+
validFor = 365 * 24 * time.Hour * 2 // 2 years
52+
rsaBits = 2048
53+
)
54+
55+
func main() {
56+
// Remove previous certificates and files
57+
fmt.Print("Remove previous certificates and files... ")
58+
os.Remove("ca.cert.pem")
59+
os.Remove("ca.key.pem")
60+
os.Remove("cert.pem")
61+
os.Remove("key.pem")
62+
fmt.Println("OK")
63+
64+
// Generate ca.key.pem
65+
fmt.Print("Generate ca.key.pem... ")
66+
caKey, err := generateKey("P256")
67+
if err != nil {
68+
log.Fatal(err)
69+
}
70+
keyOut, err := os.OpenFile("ca.key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
71+
if err != nil {
72+
log.Fatal(err)
73+
}
74+
pem.Encode(keyOut, pemBlockForKey(caKey))
75+
keyOut.Close()
76+
fmt.Println("OK")
77+
78+
// Generate ca.key.pem
79+
fmt.Print("Generate ca.pem... ")
80+
caTemplate, err := generateSingleCertificate(true)
81+
if err != nil {
82+
log.Fatal(err)
83+
}
84+
85+
derBytes, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, publicKey(caKey), caKey)
86+
87+
certOut, err := os.Create("ca.cert.pem")
88+
if err != nil {
89+
log.Fatal(err)
90+
}
91+
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
92+
certOut.Close()
93+
fmt.Println("OK")
94+
95+
// Generate key.pem
96+
fmt.Print("Generate key.pem... ")
97+
key, err := generateKey("P256")
98+
if err != nil {
99+
log.Fatal(err)
100+
}
101+
keyOut, err = os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
102+
if err != nil {
103+
log.Fatal(err)
104+
}
105+
pem.Encode(keyOut, pemBlockForKey(key))
106+
keyOut.Close()
107+
fmt.Println("OK")
108+
109+
// Generate cert.pem
110+
fmt.Print("Generate key.pem... ")
111+
template, err := generateSingleCertificate(false)
112+
if err != nil {
113+
log.Fatal(err)
114+
}
115+
derBytes, err = x509.CreateCertificate(rand.Reader, template, caTemplate, publicKey(key), caKey)
116+
certOut, err = os.Create("cert.pem")
117+
if err != nil {
118+
log.Fatal(err)
119+
}
120+
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
121+
certOut.Close()
122+
fmt.Println("OK")
123+
}
124+
125+
func generateKey(ecdsaCurve string) (interface{}, error) {
126+
switch ecdsaCurve {
127+
case "":
128+
return rsa.GenerateKey(rand.Reader, rsaBits)
129+
case "P224":
130+
return ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
131+
case "P256":
132+
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
133+
case "P384":
134+
return ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
135+
case "P521":
136+
return ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
137+
default:
138+
return nil, fmt.Errorf("Unrecognized elliptic curve: %q", ecdsaCurve)
139+
}
140+
}
141+
142+
func pemBlockForKey(priv interface{}) *pem.Block {
143+
switch k := priv.(type) {
144+
case *rsa.PrivateKey:
145+
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}
146+
case *ecdsa.PrivateKey:
147+
b, err := x509.MarshalECPrivateKey(k)
148+
if err != nil {
149+
fmt.Fprintf(os.Stderr, "Unable to marshal ECDSA private key: %v", err)
150+
os.Exit(2)
151+
}
152+
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
153+
default:
154+
return nil
155+
}
156+
}
157+
158+
func generateSingleCertificate(isCa bool) (*x509.Certificate, error) {
159+
var notBefore time.Time
160+
var err error
161+
if len(validFrom) == 0 {
162+
notBefore = time.Now()
163+
} else {
164+
notBefore, err = time.Parse("Jan 2 15:04:05 2006", validFrom)
165+
if err != nil {
166+
return nil, fmt.Errorf("Failed to parse creation date: %s\n", err.Error())
167+
}
168+
}
169+
170+
notAfter := notBefore.Add(validFor)
171+
172+
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
173+
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
174+
if err != nil {
175+
return nil, fmt.Errorf("failed to generate serial number: %s\n", err.Error())
176+
}
177+
178+
template := x509.Certificate{
179+
SerialNumber: serialNumber,
180+
Subject: pkix.Name{
181+
Organization: []string{"Arduino LLC US"},
182+
Country: []string{"US"},
183+
CommonName: "localhost",
184+
OrganizationalUnit: []string{"IT"},
185+
},
186+
NotBefore: notBefore,
187+
NotAfter: notAfter,
188+
189+
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
190+
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
191+
BasicConstraintsValid: true,
192+
}
193+
194+
hosts := strings.Split(host, ",")
195+
for _, h := range hosts {
196+
if ip := net.ParseIP(h); ip != nil {
197+
template.IPAddresses = append(template.IPAddresses, ip)
198+
} else {
199+
template.DNSNames = append(template.DNSNames, h)
200+
}
201+
}
202+
203+
if isCa {
204+
template.IsCA = true
205+
template.KeyUsage |= x509.KeyUsageCertSign
206+
template.Subject.CommonName = "Arduino"
207+
}
208+
209+
return &template, nil
210+
}
211+
212+
func publicKey(priv interface{}) interface{} {
213+
switch k := priv.(type) {
214+
case *rsa.PrivateKey:
215+
return &k.PublicKey
216+
case *ecdsa.PrivateKey:
217+
return &k.PublicKey
218+
default:
219+
return nil
220+
}
221+
}

0 commit comments

Comments
 (0)