Skip to content

Instantly share code, notes, and snippets.

@tom-code
Created December 22, 2018 00:21
Show Gist options
  • Select an option

  • Save tom-code/5395ca572b0bab6c4ce52395e76018b4 to your computer and use it in GitHub Desktop.

Select an option

Save tom-code/5395ca572b0bab6c4ce52395e76018b4 to your computer and use it in GitHub Desktop.
acme experiment
package main
import (
"fmt"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"os"
"encoding/pem"
"net/http"
"encoding/json"
"gopkg.in/square/go-jose.v2"
"strings"
)
func writeKey(path string, k *ecdsa.PrivateKey) error {
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
bytes, err := x509.MarshalECPrivateKey(k)
if err != nil {
return err
}
b := &pem.Block{Type: "EC PRIVATE KEY", Bytes: bytes}
if err := pem.Encode(f, b); err != nil {
f.Close()
return err
}
return f.Close()
}
type Directory struct {
NewAccount string
NewNonce string
NewOrder string
}
func discover(url string) *Directory {
resp, err := http.Get(url)
if err != nil {
panic(err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
fmt.Printf("can't get %s\n", url)
fmt.Println(resp)
return nil
}
var js struct {
NewAccount string `json:"newAccount"`
NewNonce string `json:"newNonce"`
NewOrder string `json:"newOrder"`
RevokeCert string `json:"revokeCert"`
Meta struct {
Terms string `json:"termsOfService"`
}
}
decoder := json.NewDecoder(resp.Body)
err = decoder.Decode(&js)
if err != nil {
panic(err)
}
return &Directory{NewAccount: js.NewAccount, NewNonce: js.NewNonce, NewOrder: js.NewOrder}
}
func getNonce(url string) string {
resp, err := http.Get(url)
if err != nil {
panic(err)
}
resp.Body.Close()
return resp.Header.Get("replay-nonce")
}
type Order struct {
Authorizations []string
FinalizeUrl string
OrderUrl string
}
func parseOrderResponse(resp *http.Response) *Order {
orderLocation := resp.Header.Get("Location")
fmt.Printf("order location %s\n", orderLocation)
var js struct {
Identifiers []struct {
Type string `json:"type"`
Value string `json:"value"`
} `json:"Identifiers"`
Authorizations []string `json:"authorizations"`
Finalize string `json:"finalize"`
}
decoder := json.NewDecoder(resp.Body)
err := decoder.Decode(&js)
if err != nil {
panic(err)
}
return &Order {Authorizations: js.Authorizations, FinalizeUrl: js.Finalize, OrderUrl: orderLocation}
}
type AuthChallenge struct {
Type string
Url string
Token string
}
func parseAuthzChalenge(resp *http.Response) *AuthChallenge {
var js struct {
Challenges []struct {
Type string `json:"type"`
Url string `json:"url"`
Token string `json:"token"`
} `json:"challenges"`
}
decoder := json.NewDecoder(resp.Body)
err := decoder.Decode(&js)
if err != nil {
panic(err)
}
fmt.Println(js)
for _, ch := range(js.Challenges) {
if ch.Type == "http-01" {
return &AuthChallenge{Type: ch.Type, Url: ch.Url, Token: ch.Token}
}
}
return nil
}
type NS struct {
nonce string
}
func (ns NS)Nonce()(string, error) {
return ns.nonce, nil
}
func postJWS(key *ecdsa.PrivateKey, url string, payload string, nonce string, kid string) *http.Response {
jsonWebKey := jose.JSONWebKey{
Key: key,
//KeyID: kid,
Algorithm: string(jose.ES256),
}
options := &jose.SignerOptions{}
options.WithHeader("url", url)
if len(kid) > 0 {
options.WithHeader("kid", kid)
} else {
options.EmbedJWK = true
}
options.NonceSource = NS{nonce}
signer, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jsonWebKey}, options)
if err != nil {
panic(err)
}
fmt.Println(signer)
jws, err := signer.Sign([]byte(payload))
if err != nil {
fmt.Println(err.Error())
}
output := jws.FullSerialize()
fmt.Println(output)
res, err := http.Post(url, "application/jose+json", strings.NewReader(output))
return res
}
func main() {
fmt.Println("generating private key...")
ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
panic(err)
}
writeKey("key.pem", ecKey)
directory := discover("http://localhost:14000/dir")
fmt.Println(directory)
nonce := getNonce(directory.NewNonce)
fmt.Printf("nonce=%s\n", nonce)
// create new account
newAcc := `{"termsOfServiceAgreed":true,"contact":["mailto:a@c.s"]}`
res := postJWS(ecKey, directory.NewAccount, newAcc, nonce, "")
account := res.Header.Get("Location")
nonce = res.Header.Get("replay-nonce")
res.Body.Close()
fmt.Println(account)
// start certificate order
newOrder := `{"identifiers": [ { "type": "dns", "value": "example.com.a" } ]}`
res = postJWS(ecKey, directory.NewOrder, newOrder, nonce, account)
nonce = res.Header.Get("replay-nonce")
order := parseOrderResponse(res)
res.Body.Close()
fmt.Println(order)
// start authorization
res = postJWS(ecKey, order.Authorizations[0], "", nonce, account)
challenge := parseAuthzChalenge(res)
nonce = res.Header.Get("replay-nonce")
res.Body.Close()
// confirm we arranged resource
res = postJWS(ecKey, challenge.Url, "{}", nonce, account)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment