The order based issuance flow is different from pre-authorization in that users tell upfront which identifiers they want a future certificate to contain and the CA responds with a set of authorizations to satisfy. Similar to pre-authorization where users start with Client's Authorize method, fulfill challenges and then call GetAuthorization or WaitAuthorization, the order based flow starts with AuthorizeOrder and then GetOrder or WaitOrder. Once all order authorizations are satisfied, users can call CreateOrderCert, as opposed to the old CreateCert, and FetchCert as before. The new method implementation and updates to the existing methods is in golang.org/cl/194379. More on order based flow can be found in https://tools.ietf.org/html/rfc8555#section-7.4. Updates golang/go#21081 Change-Id: I37c37203b50785d7681f65f815d7b19d9c15b96d Reviewed-on: https://go-review.googlesource.com/c/crypto/+/192779 Run-TryBot: Alex Vaghin <ddos@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Filippo Valsorda <filippo@golang.org>
185 lines
5.5 KiB
Go
185 lines
5.5 KiB
Go
// Copyright 2015 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package acme
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/ecdsa"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/sha256"
|
|
_ "crypto/sha512" // need for EC keys
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/big"
|
|
)
|
|
|
|
// keyID is the account identity provided by a CA during registration.
|
|
type keyID string
|
|
|
|
// noKeyID indicates that jwsEncodeJSON should compute and use JWK instead of a KID.
|
|
// See jwsEncodeJSON for details.
|
|
const noKeyID = keyID("")
|
|
|
|
// noPayload indicates jwsEncodeJSON will encode zero-length octet string
|
|
// in a JWS request. This is called POST-as-GET in RFC 8555 and is used to make
|
|
// authenticated GET requests via POSTing with an empty payload.
|
|
// See https://tools.ietf.org/html/rfc8555#section-6.3 for more details.
|
|
const noPayload = ""
|
|
|
|
// jwsEncodeJSON signs claimset using provided key and a nonce.
|
|
// The result is serialized in JSON format containing either kid or jwk
|
|
// fields based on the provided keyID value.
|
|
//
|
|
// If kid is non-empty, its quoted value is inserted in the protected head
|
|
// as "kid" field value. Otherwise, JWK is computed using jwkEncode and inserted
|
|
// as "jwk" field value. The "jwk" and "kid" fields are mutually exclusive.
|
|
//
|
|
// See https://tools.ietf.org/html/rfc7515#section-7.
|
|
func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid keyID, nonce, url string) ([]byte, error) {
|
|
alg, sha := jwsHasher(key.Public())
|
|
if alg == "" || !sha.Available() {
|
|
return nil, ErrUnsupportedKey
|
|
}
|
|
var phead string
|
|
switch kid {
|
|
case noKeyID:
|
|
jwk, err := jwkEncode(key.Public())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
phead = fmt.Sprintf(`{"alg":%q,"jwk":%s,"nonce":%q,"url":%q}`, alg, jwk, nonce, url)
|
|
default:
|
|
phead = fmt.Sprintf(`{"alg":%q,"kid":%q,"nonce":%q,"url":%q}`, alg, kid, nonce, url)
|
|
}
|
|
phead = base64.RawURLEncoding.EncodeToString([]byte(phead))
|
|
var payload string
|
|
if claimset != noPayload {
|
|
cs, err := json.Marshal(claimset)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
payload = base64.RawURLEncoding.EncodeToString(cs)
|
|
}
|
|
hash := sha.New()
|
|
hash.Write([]byte(phead + "." + payload))
|
|
sig, err := jwsSign(key, sha, hash.Sum(nil))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
enc := struct {
|
|
Protected string `json:"protected"`
|
|
Payload string `json:"payload"`
|
|
Sig string `json:"signature"`
|
|
}{
|
|
Protected: phead,
|
|
Payload: payload,
|
|
Sig: base64.RawURLEncoding.EncodeToString(sig),
|
|
}
|
|
return json.Marshal(&enc)
|
|
}
|
|
|
|
// jwkEncode encodes public part of an RSA or ECDSA key into a JWK.
|
|
// The result is also suitable for creating a JWK thumbprint.
|
|
// https://tools.ietf.org/html/rfc7517
|
|
func jwkEncode(pub crypto.PublicKey) (string, error) {
|
|
switch pub := pub.(type) {
|
|
case *rsa.PublicKey:
|
|
// https://tools.ietf.org/html/rfc7518#section-6.3.1
|
|
n := pub.N
|
|
e := big.NewInt(int64(pub.E))
|
|
// Field order is important.
|
|
// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
|
|
return fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`,
|
|
base64.RawURLEncoding.EncodeToString(e.Bytes()),
|
|
base64.RawURLEncoding.EncodeToString(n.Bytes()),
|
|
), nil
|
|
case *ecdsa.PublicKey:
|
|
// https://tools.ietf.org/html/rfc7518#section-6.2.1
|
|
p := pub.Curve.Params()
|
|
n := p.BitSize / 8
|
|
if p.BitSize%8 != 0 {
|
|
n++
|
|
}
|
|
x := pub.X.Bytes()
|
|
if n > len(x) {
|
|
x = append(make([]byte, n-len(x)), x...)
|
|
}
|
|
y := pub.Y.Bytes()
|
|
if n > len(y) {
|
|
y = append(make([]byte, n-len(y)), y...)
|
|
}
|
|
// Field order is important.
|
|
// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
|
|
return fmt.Sprintf(`{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`,
|
|
p.Name,
|
|
base64.RawURLEncoding.EncodeToString(x),
|
|
base64.RawURLEncoding.EncodeToString(y),
|
|
), nil
|
|
}
|
|
return "", ErrUnsupportedKey
|
|
}
|
|
|
|
// jwsSign signs the digest using the given key.
|
|
// The hash is unused for ECDSA keys.
|
|
//
|
|
// Note: non-stdlib crypto.Signer implementations are expected to return
|
|
// the signature in the format as specified in RFC7518.
|
|
// See https://tools.ietf.org/html/rfc7518 for more details.
|
|
func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) {
|
|
if key, ok := key.(*ecdsa.PrivateKey); ok {
|
|
// The key.Sign method of ecdsa returns ASN1-encoded signature.
|
|
// So, we use the package Sign function instead
|
|
// to get R and S values directly and format the result accordingly.
|
|
r, s, err := ecdsa.Sign(rand.Reader, key, digest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rb, sb := r.Bytes(), s.Bytes()
|
|
size := key.Params().BitSize / 8
|
|
if size%8 > 0 {
|
|
size++
|
|
}
|
|
sig := make([]byte, size*2)
|
|
copy(sig[size-len(rb):], rb)
|
|
copy(sig[size*2-len(sb):], sb)
|
|
return sig, nil
|
|
}
|
|
return key.Sign(rand.Reader, digest, hash)
|
|
}
|
|
|
|
// jwsHasher indicates suitable JWS algorithm name and a hash function
|
|
// to use for signing a digest with the provided key.
|
|
// It returns ("", 0) if the key is not supported.
|
|
func jwsHasher(pub crypto.PublicKey) (string, crypto.Hash) {
|
|
switch pub := pub.(type) {
|
|
case *rsa.PublicKey:
|
|
return "RS256", crypto.SHA256
|
|
case *ecdsa.PublicKey:
|
|
switch pub.Params().Name {
|
|
case "P-256":
|
|
return "ES256", crypto.SHA256
|
|
case "P-384":
|
|
return "ES384", crypto.SHA384
|
|
case "P-521":
|
|
return "ES512", crypto.SHA512
|
|
}
|
|
}
|
|
return "", 0
|
|
}
|
|
|
|
// JWKThumbprint creates a JWK thumbprint out of pub
|
|
// as specified in https://tools.ietf.org/html/rfc7638.
|
|
func JWKThumbprint(pub crypto.PublicKey) (string, error) {
|
|
jwk, err := jwkEncode(pub)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
b := sha256.Sum256([]byte(jwk))
|
|
return base64.RawURLEncoding.EncodeToString(b[:]), nil
|
|
}
|