Created
November 1, 2022 09:14
-
-
Save yteraoka/350836c45ce34ecf7119cd58de20a168 to your computer and use it in GitHub Desktop.
golang.org/x/crypto/acme を使って証明書を発行する例
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bufio" | |
"context" | |
"crypto" | |
"crypto/ecdsa" | |
"crypto/rand" | |
"crypto/rsa" | |
"crypto/x509" | |
"crypto/x509/pkix" | |
"encoding/pem" | |
"errors" | |
"flag" | |
"fmt" | |
"golang.org/x/crypto/acme" | |
"log" | |
"os" | |
"strings" | |
) | |
func parsePrivateKey(der []byte) (crypto.Signer, error) { | |
if key, err := x509.ParsePKCS1PrivateKey(der); err == nil { | |
return key, nil | |
} | |
if key, err := x509.ParsePKCS8PrivateKey(der); err == nil { | |
switch key := key.(type) { | |
case *rsa.PrivateKey: | |
return key, nil | |
case *ecdsa.PrivateKey: | |
return key, nil | |
default: | |
return nil, errors.New("unknown private key type") | |
} | |
} | |
if key, err := x509.ParseECPrivateKey(der); err == nil { | |
return key, nil | |
} | |
return nil, errors.New("failed to parse private key") | |
} | |
func main() { | |
var ( | |
keyFile = flag.String("key-file", "", "account key file") | |
email = flag.String("email", "", "email address") | |
domain = flag.String("domain", "", "domain name") | |
orderURI = flag.String("order-uri", "", "Order URI") | |
useStaging = flag.Bool("staging", false, "Use Let's Encrypt Staging Endpoint") | |
) | |
flag.Parse() | |
var err error | |
if *domain == "" { | |
log.Fatal("domain is required") | |
} | |
block := &pem.Block{} | |
var acctKey crypto.Signer | |
if *keyFile != "" { | |
bytes, err := os.ReadFile(*keyFile) | |
if err != nil { | |
log.Fatal(err) | |
} | |
block, _ = pem.Decode(bytes) | |
} else { | |
if *email == "" { | |
log.Fatal("email is required") | |
} | |
k, err := rsa.GenerateKey(rand.Reader, 2048) | |
if err != nil { | |
log.Fatal(err) | |
} | |
block = &pem.Block{ | |
Type: "RSA PRIVATE KEY", | |
Bytes: x509.MarshalPKCS1PrivateKey(k), | |
} | |
f, err := os.Create("acme.key") | |
if err != nil { | |
log.Fatal(err) | |
} | |
if err := pem.Encode(f, block); err != nil { | |
log.Fatal(err) | |
} | |
f.Close() | |
fmt.Println("Saved new account private key into acme.key") | |
} | |
if block == nil { | |
log.Fatal("failed to decode PEM block containing private key") | |
} | |
if strings.Contains(block.Type, "PRIVATE") { | |
acctKey, err = parsePrivateKey(block.Bytes) | |
} else { | |
log.Fatal("Unknown private key type: %s", block.Type) | |
} | |
client := &acme.Client{ | |
Key: acctKey, | |
} | |
// staging 環境を使う場合は DirectoryURL に https://acme-staging-v02.api.letsencrypt.org/directory を指定する | |
if *useStaging { | |
client.DirectoryURL = "https://acme-staging-v02.api.letsencrypt.org/directory" | |
} | |
fmt.Printf("### acme.Client ###\n%#v\n\n", client) | |
ctx := context.Background() | |
acct := &acme.Account{} | |
if *keyFile == "" { | |
acct.Contact = append(acct.Contact, "mailto:"+*email) | |
fmt.Printf("### Account ###\n%#v\n\n", acct) | |
acct, err = client.Register(ctx, acct, acme.AcceptTOS) | |
if err != nil { | |
log.Fatal(err) | |
} | |
} else { | |
// すでに Let's Encrypt のアカウントを持っている場合は PrivateKey から Account を取得 | |
acct, err := client.GetReg(ctx, "") | |
if err != nil { | |
log.Fatal(err) | |
} | |
fmt.Printf("### Account ###\n%#v\n\n", acct) | |
} | |
directory, err := client.Discover(ctx) | |
if err != nil { | |
log.Fatal(err) | |
} | |
fmt.Printf("### Directory ###\n%#v\n\n", directory) | |
order := &acme.Order{} | |
if *orderURI == "" { | |
order, err = client.AuthorizeOrder(ctx, acme.DomainIDs(*domain)) | |
} else { | |
order, err = client.GetOrder(ctx, *orderURI) | |
} | |
if err != nil { | |
log.Fatal(err) | |
} | |
fmt.Printf("### AuthorizeOrder ###\n%#v\n\n", order) | |
if order.Error != nil { | |
log.Fatal(order.Error.Error()) | |
} | |
for i, url := range order.AuthzURLs { | |
authz, err := client.GetAuthorization(ctx, url) | |
if err != nil { | |
log.Fatal(err) | |
} | |
fmt.Printf("### Authorization[%d] ###\n%#v\n\n", i, authz) | |
for j, c := range authz.Challenges { | |
fmt.Printf("### Challenge[%d] ###\n%#v\n\n", j, c) | |
if c.Type == "http-01" { | |
path := client.HTTP01ChallengePath(c.Token) | |
fmt.Printf("### HTTP01ChallengePath ###\n%s\n\n", path) | |
response, err := client.HTTP01ChallengeResponse(c.Token) | |
if err != nil { | |
log.Fatal(err) | |
} | |
fmt.Printf("### HTTP01ChallengeResponse ###\n%s\n\n", response) | |
if c.Status == "pending" { | |
scanner := bufio.NewScanner(os.Stdin) | |
fmt.Printf("Type `ENTER` if you are ready: ") | |
scanner.Scan() | |
// Accept すると challenge の URL にアクセスが来る | |
fmt.Println("Going to accept challenge") | |
c2, err := client.Accept(ctx, c) | |
if err != nil { | |
log.Fatal(err) | |
} | |
fmt.Printf("### Accept Response ###\n%s\n\n", c2) | |
} | |
} | |
} | |
// Accept したら Authorization の Status が ready になるまで待つ | |
if authz.Status == "pending" { | |
fmt.Printf("### Starting WaitAuthorization ###\n") | |
a, err := client.WaitAuthorization(ctx, url) | |
if err != nil { | |
log.Fatal(err) | |
} | |
fmt.Printf("### WaitAuthorization Response ###\n#%v\n\n", a) | |
} | |
} | |
// Order の Status が ready になるのを待つ | |
if order.Status == "pending" { | |
fmt.Printf("### Starting WaitOrder ###\n") | |
if *orderURI == "" { | |
*orderURI = order.URI | |
} | |
order, err = client.WaitOrder(ctx, *orderURI) | |
if err != nil { | |
log.Fatal(err) | |
} | |
fmt.Printf("### WaitOrder Response ###\n%#v\n\n", order) | |
} | |
// Private Key の作成を保存 | |
key, err := rsa.GenerateKey(rand.Reader, 2048) | |
if err != nil { | |
log.Fatal(err) | |
} | |
f, err := os.Create(*domain + ".key") | |
if err != nil { | |
log.Fatal(err) | |
} | |
err = pem.Encode(f, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}) | |
if err != nil { | |
log.Fatal(err) | |
} | |
f.Close() | |
// CSR 作成 | |
req := &x509.CertificateRequest{ | |
Subject: pkix.Name{CommonName: *domain}, | |
DNSNames: []string{*domain}, | |
} | |
csr, err := x509.CreateCertificateRequest(rand.Reader, req, key) | |
if err != nil { | |
log.Fatal(err) | |
} | |
fmt.Printf("%#v\n", csr) | |
// 証明書発行リクエスト | |
der, certURL, err := client.CreateOrderCert(ctx, order.FinalizeURL, csr, true) | |
if err != nil { | |
log.Fatal(err) | |
} | |
f, err = os.Create(*domain + ".crt") | |
if err != nil { | |
log.Fatal(err) | |
} | |
for _, b := range der { | |
block := &pem.Block{ | |
Type: "CERTIFICATE", | |
Bytes: b, | |
} | |
if err := pem.Encode(os.Stdout, block); err != nil { | |
log.Fatal(err) | |
} | |
if err := pem.Encode(f, block); err != nil { | |
log.Fatal(err) | |
} | |
} | |
f.Close() | |
fmt.Printf("certURL: %s\n", certURL) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment