674 lines
20 KiB
Go
674 lines
20 KiB
Go
package core
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/subtle"
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/letsencrypt/boulder/probs"
|
|
"github.com/square/go-jose"
|
|
)
|
|
|
|
// AcmeStatus defines the state of a given authorization
|
|
type AcmeStatus string
|
|
|
|
// AcmeResource values identify different types of ACME resources
|
|
type AcmeResource string
|
|
|
|
// Buffer is a variable-length collection of bytes
|
|
type Buffer []byte
|
|
|
|
// IdentifierType defines the available identification mechanisms for domains
|
|
type IdentifierType string
|
|
|
|
// OCSPStatus defines the state of OCSP for a domain
|
|
type OCSPStatus string
|
|
|
|
// These statuses are the states of authorizations
|
|
const (
|
|
StatusUnknown = AcmeStatus("unknown") // Unknown status; the default
|
|
StatusPending = AcmeStatus("pending") // In process; client has next action
|
|
StatusProcessing = AcmeStatus("processing") // In process; server has next action
|
|
StatusValid = AcmeStatus("valid") // Validation succeeded
|
|
StatusInvalid = AcmeStatus("invalid") // Validation failed
|
|
StatusRevoked = AcmeStatus("revoked") // Object no longer valid
|
|
)
|
|
|
|
// These types are the available identification mechanisms
|
|
const (
|
|
IdentifierDNS = IdentifierType("dns")
|
|
)
|
|
|
|
// The types of ACME resources
|
|
const (
|
|
ResourceNewReg = AcmeResource("new-reg")
|
|
ResourceNewAuthz = AcmeResource("new-authz")
|
|
ResourceNewCert = AcmeResource("new-cert")
|
|
ResourceRevokeCert = AcmeResource("revoke-cert")
|
|
ResourceRegistration = AcmeResource("reg")
|
|
ResourceChallenge = AcmeResource("challenge")
|
|
)
|
|
|
|
// These status are the states of OCSP
|
|
const (
|
|
OCSPStatusGood = OCSPStatus("good")
|
|
OCSPStatusRevoked = OCSPStatus("revoked")
|
|
)
|
|
|
|
// These types are the available challenges
|
|
const (
|
|
ChallengeTypeHTTP01 = "http-01"
|
|
ChallengeTypeTLSSNI01 = "tls-sni-01"
|
|
ChallengeTypeDNS01 = "dns-01"
|
|
)
|
|
|
|
// ValidChallenge tests whether the provided string names a known challenge
|
|
func ValidChallenge(name string) bool {
|
|
switch name {
|
|
case ChallengeTypeHTTP01:
|
|
fallthrough
|
|
case ChallengeTypeTLSSNI01:
|
|
fallthrough
|
|
case ChallengeTypeDNS01:
|
|
return true
|
|
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// TLSSNISuffix is appended to pseudo-domain names in DVSNI challenges
|
|
const TLSSNISuffix = "acme.invalid"
|
|
|
|
// DNSPrefix is attached to DNS names in DNS challenges
|
|
const DNSPrefix = "_acme-challenge"
|
|
|
|
// An AcmeIdentifier encodes an identifier that can
|
|
// be validated by ACME. The protocol allows for different
|
|
// types of identifier to be supported (DNS names, IP
|
|
// addresses, etc.), but currently we only support
|
|
// domain names.
|
|
type AcmeIdentifier struct {
|
|
Type IdentifierType `json:"type"` // The type of identifier being encoded
|
|
Value string `json:"value"` // The identifier itself
|
|
}
|
|
|
|
// CertificateRequest is just a CSR
|
|
//
|
|
// This data is unmarshalled from JSON by way of rawCertificateRequest, which
|
|
// represents the actual structure received from the client.
|
|
type CertificateRequest struct {
|
|
CSR *x509.CertificateRequest // The CSR
|
|
Bytes []byte // The original bytes of the CSR, for logging.
|
|
}
|
|
|
|
type rawCertificateRequest struct {
|
|
CSR JSONBuffer `json:"csr"` // The encoded CSR
|
|
}
|
|
|
|
// UnmarshalJSON provides an implementation for decoding CertificateRequest objects.
|
|
func (cr *CertificateRequest) UnmarshalJSON(data []byte) error {
|
|
var raw rawCertificateRequest
|
|
if err := json.Unmarshal(data, &raw); err != nil {
|
|
return err
|
|
}
|
|
|
|
csr, err := x509.ParseCertificateRequest(raw.CSR)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cr.CSR = csr
|
|
cr.Bytes = raw.CSR
|
|
return nil
|
|
}
|
|
|
|
// MarshalJSON provides an implementation for encoding CertificateRequest objects.
|
|
func (cr CertificateRequest) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(rawCertificateRequest{
|
|
CSR: cr.CSR.Raw,
|
|
})
|
|
}
|
|
|
|
// Registration objects represent non-public metadata attached
|
|
// to account keys.
|
|
type Registration struct {
|
|
// Unique identifier
|
|
ID int64 `json:"id" db:"id"`
|
|
|
|
// Account key to which the details are attached
|
|
Key jose.JsonWebKey `json:"key"`
|
|
|
|
// Contact URIs
|
|
Contact []*AcmeURL `json:"contact,omitempty"`
|
|
|
|
// Agreement with terms of service
|
|
Agreement string `json:"agreement,omitempty"`
|
|
|
|
// InitialIP is the IP address from which the registration was created
|
|
InitialIP net.IP `json:"initialIp"`
|
|
|
|
// CreatedAt is the time the registration was created.
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
}
|
|
|
|
// MergeUpdate copies a subset of information from the input Registration
|
|
// into this one.
|
|
func (r *Registration) MergeUpdate(input Registration) {
|
|
if len(input.Contact) > 0 {
|
|
r.Contact = input.Contact
|
|
}
|
|
|
|
if len(input.Agreement) > 0 {
|
|
r.Agreement = input.Agreement
|
|
}
|
|
}
|
|
|
|
// ValidationRecord represents a validation attempt against a specific URL/hostname
|
|
// and the IP addresses that were resolved and used
|
|
type ValidationRecord struct {
|
|
// DNS only
|
|
Authorities []string `json:",omitempty"`
|
|
|
|
// SimpleHTTP only
|
|
URL string `json:"url,omitempty"`
|
|
|
|
// Shared
|
|
Hostname string `json:"hostname"`
|
|
Port string `json:"port"`
|
|
AddressesResolved []net.IP `json:"addressesResolved"`
|
|
AddressUsed net.IP `json:"addressUsed"`
|
|
}
|
|
|
|
// KeyAuthorization represents a domain holder's authorization for a
|
|
// specific account key to satisfy a specific challenge.
|
|
type KeyAuthorization struct {
|
|
Token string
|
|
Thumbprint string
|
|
}
|
|
|
|
// NewKeyAuthorization computes the thumbprint and assembles the object
|
|
func NewKeyAuthorization(token string, key *jose.JsonWebKey) (KeyAuthorization, error) {
|
|
if key == nil {
|
|
return KeyAuthorization{}, fmt.Errorf("Cannot authorize a nil key")
|
|
}
|
|
|
|
thumbprint, err := key.Thumbprint(crypto.SHA256)
|
|
if err != nil {
|
|
return KeyAuthorization{}, err
|
|
}
|
|
|
|
return KeyAuthorization{
|
|
Token: token,
|
|
Thumbprint: base64.RawURLEncoding.EncodeToString(thumbprint),
|
|
}, nil
|
|
}
|
|
|
|
// String produces the string representation of a key authorization
|
|
func (ka KeyAuthorization) String() string {
|
|
return ka.Token + "." + ka.Thumbprint
|
|
}
|
|
|
|
// Match determines whether this KeyAuthorization matches the given token and key
|
|
func (ka KeyAuthorization) Match(token string, key *jose.JsonWebKey) bool {
|
|
if key == nil {
|
|
return false
|
|
}
|
|
|
|
thumbprintBytes, err := key.Thumbprint(crypto.SHA256)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
thumbprint := base64.RawURLEncoding.EncodeToString(thumbprintBytes)
|
|
|
|
tokensEqual := subtle.ConstantTimeCompare([]byte(token), []byte(ka.Token))
|
|
thumbprintsEqual := subtle.ConstantTimeCompare([]byte(thumbprint), []byte(ka.Thumbprint))
|
|
|
|
return tokensEqual == 1 && thumbprintsEqual == 1
|
|
}
|
|
|
|
// MarshalJSON packs a key authorization into its string representation
|
|
func (ka KeyAuthorization) MarshalJSON() (result []byte, err error) {
|
|
return json.Marshal(ka.String())
|
|
}
|
|
|
|
// UnmarshalJSON unpacks a key authorization from a string
|
|
func (ka *KeyAuthorization) UnmarshalJSON(data []byte) (err error) {
|
|
var str string
|
|
err = json.Unmarshal(data, &str)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
parts := strings.Split(str, ".")
|
|
if len(parts) != 2 {
|
|
return fmt.Errorf("Invalid key authorization: does not look like a key authorization")
|
|
} else if !LooksLikeAToken(parts[0]) {
|
|
return fmt.Errorf("Invalid key authorization: malformed token")
|
|
} else if !LooksLikeAToken(parts[1]) {
|
|
// Thumbprints have the same syntax as tokens in boulder
|
|
// Both are base64-encoded and 32 octets
|
|
return fmt.Errorf("Invalid key authorization: malformed key thumbprint")
|
|
}
|
|
|
|
ka.Token = parts[0]
|
|
ka.Thumbprint = parts[1]
|
|
return
|
|
}
|
|
|
|
// Challenge is an aggregate of all data needed for any challenges.
|
|
//
|
|
// Rather than define individual types for different types of
|
|
// challenge, we just throw all the elements into one bucket,
|
|
// together with the common metadata elements.
|
|
type Challenge struct {
|
|
ID int64 `json:"id,omitempty"`
|
|
|
|
// The type of challenge
|
|
Type string `json:"type"`
|
|
|
|
// The status of this challenge
|
|
Status AcmeStatus `json:"status,omitempty"`
|
|
|
|
// Contains the error that occurred during challenge validation, if any
|
|
Error *probs.ProblemDetails `json:"error,omitempty"`
|
|
|
|
// A URI to which a response can be POSTed
|
|
URI string `json:"uri"`
|
|
|
|
// Used by http-01, tls-sni-01, and dns-01 challenges
|
|
Token string `json:"token,omitempty"` // Used by http-00, tls-sni-00, and dns-00 challenges
|
|
|
|
// The KeyAuthorization provided by the client to start validation of
|
|
// the challenge. Set during
|
|
//
|
|
// POST /acme/authz/:authzid/:challid
|
|
//
|
|
// Used by http-01, tls-sni-01, and dns-01 challenges
|
|
ProvidedKeyAuthorization string `json:"keyAuthorization,omitempty"`
|
|
|
|
// Contains information about URLs used or redirected to and IPs resolved and
|
|
// used
|
|
ValidationRecord []ValidationRecord `json:"validationRecord,omitempty"`
|
|
|
|
// The account key used to create this challenge. This is not part of the
|
|
// spec, but clients are required to ignore unknown fields, so it's harmless
|
|
// to include.
|
|
//
|
|
// Boulder needs to remember what key was used to create a challenge in order
|
|
// to prevent an attacker from re-using a validation signature with a different,
|
|
// unauthorized key. See:
|
|
// https://mailarchive.ietf.org/arch/msg/acme/F71iz6qq1o_QPVhJCV4dqWf-4Yc
|
|
AccountKey *jose.JsonWebKey `json:"accountKey,omitempty"`
|
|
}
|
|
|
|
// ExpectedKeyAuthorization computes the expected KeyAuthorization value for
|
|
// the challenge.
|
|
func (ch Challenge) ExpectedKeyAuthorization() (string, error) {
|
|
expectedKA, err := NewKeyAuthorization(ch.Token, ch.AccountKey)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return expectedKA.String(), nil
|
|
}
|
|
|
|
// RecordsSane checks the sanity of a ValidationRecord object before sending it
|
|
// back to the RA to be stored.
|
|
func (ch Challenge) RecordsSane() bool {
|
|
if ch.ValidationRecord == nil || len(ch.ValidationRecord) == 0 {
|
|
return false
|
|
}
|
|
|
|
switch ch.Type {
|
|
case ChallengeTypeHTTP01:
|
|
for _, rec := range ch.ValidationRecord {
|
|
if rec.URL == "" || rec.Hostname == "" || rec.Port == "" || rec.AddressUsed == nil ||
|
|
len(rec.AddressesResolved) == 0 {
|
|
return false
|
|
}
|
|
}
|
|
case ChallengeTypeTLSSNI01:
|
|
if len(ch.ValidationRecord) > 1 {
|
|
return false
|
|
}
|
|
if ch.ValidationRecord[0].URL != "" {
|
|
return false
|
|
}
|
|
if ch.ValidationRecord[0].Hostname == "" || ch.ValidationRecord[0].Port == "" ||
|
|
ch.ValidationRecord[0].AddressUsed == nil || len(ch.ValidationRecord[0].AddressesResolved) == 0 {
|
|
return false
|
|
}
|
|
case ChallengeTypeDNS01:
|
|
if len(ch.ValidationRecord) > 1 {
|
|
return false
|
|
}
|
|
if ch.ValidationRecord[0].Hostname == "" {
|
|
return false
|
|
}
|
|
return true
|
|
default: // Unsupported challenge type
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// IsSaneForClientOffer checks the fields of a challenge object before it is
|
|
// given to the client.
|
|
//
|
|
// This function is an alias of Challenge.IsSane(false).
|
|
func (ch Challenge) IsSaneForClientOffer() bool {
|
|
return ch.IsSane(false)
|
|
}
|
|
|
|
// IsSaneForValidation checks the fields of a challenge object before it is
|
|
// given to the VA.
|
|
//
|
|
// This function is an alias of Challenge.IsSane(false).
|
|
func (ch Challenge) IsSaneForValidation() bool {
|
|
return ch.IsSane(true)
|
|
}
|
|
|
|
// IsSane checks the sanity of a challenge object before issued to the client
|
|
// (completed = false) and before validation (completed = true).
|
|
func (ch Challenge) IsSane(completed bool) bool {
|
|
if ch.Status != StatusPending {
|
|
return false
|
|
}
|
|
|
|
// There always needs to be an account key and token
|
|
if ch.AccountKey == nil || !LooksLikeAToken(ch.Token) {
|
|
return false
|
|
}
|
|
|
|
// Before completion, the key authorization field should be empty
|
|
if !completed && ch.ProvidedKeyAuthorization != "" {
|
|
return false
|
|
}
|
|
|
|
// If the challenge is completed, then there should be a key authorization,
|
|
// and it should match the challenge.
|
|
if completed {
|
|
if ch.ProvidedKeyAuthorization == "" {
|
|
return false
|
|
}
|
|
|
|
expectedKA, err := ch.ExpectedKeyAuthorization()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if ch.ProvidedKeyAuthorization != expectedKA {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Authorization represents the authorization of an account key holder
|
|
// to act on behalf of a domain. This struct is intended to be used both
|
|
// internally and for JSON marshaling on the wire. Any fields that should be
|
|
// suppressed on the wire (e.g., ID, regID) must be made empty before marshaling.
|
|
type Authorization struct {
|
|
// An identifier for this authorization, unique across
|
|
// authorizations and certificates within this instance.
|
|
ID string `json:"id,omitempty" db:"id"`
|
|
|
|
// The identifier for which authorization is being given
|
|
Identifier AcmeIdentifier `json:"identifier,omitempty" db:"identifier"`
|
|
|
|
// The registration ID associated with the authorization
|
|
RegistrationID int64 `json:"regId,omitempty" db:"registrationID"`
|
|
|
|
// The status of the validation of this authorization
|
|
Status AcmeStatus `json:"status,omitempty" db:"status"`
|
|
|
|
// The date after which this authorization will be no
|
|
// longer be considered valid. Note: a certificate may be issued even on the
|
|
// last day of an authorization's lifetime. The last day for which someone can
|
|
// hold a valid certificate based on an authorization is authorization
|
|
// lifetime + certificate lifetime.
|
|
Expires *time.Time `json:"expires,omitempty" db:"expires"`
|
|
|
|
// An array of challenges objects used to validate the
|
|
// applicant's control of the identifier. For authorizations
|
|
// in process, these are challenges to be fulfilled; for
|
|
// final authorizations, they describe the evidence that
|
|
// the server used in support of granting the authorization.
|
|
Challenges []Challenge `json:"challenges,omitempty" db:"-"`
|
|
|
|
// The server may suggest combinations of challenges if it
|
|
// requires more than one challenge to be completed.
|
|
Combinations [][]int `json:"combinations,omitempty" db:"combinations"`
|
|
}
|
|
|
|
// FindChallenge will look for the given challenge inside this authorization. If
|
|
// found, it will return the index of that challenge within the Authorization's
|
|
// Challenges array. Otherwise it will return -1.
|
|
func (authz *Authorization) FindChallenge(challengeID int64) int {
|
|
for i, c := range authz.Challenges {
|
|
if c.ID == challengeID {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// JSONBuffer fields get encoded and decoded JOSE-style, in base64url encoding
|
|
// with stripped padding.
|
|
type JSONBuffer []byte
|
|
|
|
// URL-safe base64 encode that strips padding
|
|
func base64URLEncode(data []byte) string {
|
|
var result = base64.URLEncoding.EncodeToString(data)
|
|
return strings.TrimRight(result, "=")
|
|
}
|
|
|
|
// URL-safe base64 decoder that adds padding
|
|
func base64URLDecode(data string) ([]byte, error) {
|
|
var missing = (4 - len(data)%4) % 4
|
|
data += strings.Repeat("=", missing)
|
|
return base64.URLEncoding.DecodeString(data)
|
|
}
|
|
|
|
// MarshalJSON encodes a JSONBuffer for transmission.
|
|
func (jb JSONBuffer) MarshalJSON() (result []byte, err error) {
|
|
return json.Marshal(base64URLEncode(jb))
|
|
}
|
|
|
|
// UnmarshalJSON decodes a JSONBuffer to an object.
|
|
func (jb *JSONBuffer) UnmarshalJSON(data []byte) (err error) {
|
|
var str string
|
|
err = json.Unmarshal(data, &str)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
*jb, err = base64URLDecode(str)
|
|
return
|
|
}
|
|
|
|
// Certificate objects are entirely internal to the server. The only
|
|
// thing exposed on the wire is the certificate itself.
|
|
type Certificate struct {
|
|
RegistrationID int64 `db:"registrationID"`
|
|
|
|
Serial string `db:"serial"`
|
|
Digest string `db:"digest"`
|
|
DER []byte `db:"der"`
|
|
Issued time.Time `db:"issued"`
|
|
Expires time.Time `db:"expires"`
|
|
}
|
|
|
|
// IdentifierData holds information about what certificates are known for a
|
|
// given identifier. This is used to present Proof of Possession challenges in
|
|
// the case where a certificate already exists. The DB table holding
|
|
// IdentifierData rows contains information about certs issued by Boulder and
|
|
// also information about certs observed from third parties.
|
|
type IdentifierData struct {
|
|
ReversedName string `db:"reversedName"` // The label-wise reverse of an identifier, e.g. com.example or com.example.*
|
|
CertSHA1 string `db:"certSHA1"` // The hex encoding of the SHA-1 hash of a cert containing the identifier
|
|
}
|
|
|
|
// CertificateStatus structs are internal to the server. They represent the
|
|
// latest data about the status of the certificate, required for OCSP updating
|
|
// and for validating that the subscriber has accepted the certificate.
|
|
type CertificateStatus struct {
|
|
Serial string `db:"serial"`
|
|
|
|
// subscriberApproved: true iff the subscriber has posted back to the server
|
|
// that they accept the certificate, otherwise 0.
|
|
SubscriberApproved bool `db:"subscriberApproved"`
|
|
|
|
// status: 'good' or 'revoked'. Note that good, expired certificates remain
|
|
// with status 'good' but don't necessarily get fresh OCSP responses.
|
|
Status OCSPStatus `db:"status"`
|
|
|
|
// ocspLastUpdated: The date and time of the last time we generated an OCSP
|
|
// response. If we have never generated one, this has the zero value of
|
|
// time.Time, i.e. Jan 1 1970.
|
|
OCSPLastUpdated time.Time `db:"ocspLastUpdated"`
|
|
|
|
// revokedDate: If status is 'revoked', this is the date and time it was
|
|
// revoked. Otherwise it has the zero value of time.Time, i.e. Jan 1 1970.
|
|
RevokedDate time.Time `db:"revokedDate"`
|
|
|
|
// revokedReason: If status is 'revoked', this is the reason code for the
|
|
// revocation. Otherwise it is zero (which happens to be the reason
|
|
// code for 'unspecified').
|
|
RevokedReason RevocationCode `db:"revokedReason"`
|
|
|
|
LastExpirationNagSent time.Time `db:"lastExpirationNagSent"`
|
|
|
|
// The encoded and signed OCSP response.
|
|
OCSPResponse []byte `db:"ocspResponse"`
|
|
|
|
LockCol int64 `json:"-"`
|
|
}
|
|
|
|
// OCSPResponse is a (large) table of OCSP responses. This contains all
|
|
// historical OCSP responses we've signed, is append-only, and is likely to get
|
|
// quite large.
|
|
// It must be administratively truncated outside of Boulder.
|
|
type OCSPResponse struct {
|
|
ID int `db:"id"`
|
|
|
|
// serial: Same as certificate serial.
|
|
Serial string `db:"serial"`
|
|
|
|
// createdAt: The date the response was signed.
|
|
CreatedAt time.Time `db:"createdAt"`
|
|
|
|
// response: The encoded and signed CRL.
|
|
Response []byte `db:"response"`
|
|
}
|
|
|
|
// CRL is a large table of signed CRLs. This contains all historical CRLs
|
|
// we've signed, is append-only, and is likely to get quite large.
|
|
// It must be administratively truncated outside of Boulder.
|
|
type CRL struct {
|
|
// serial: Same as certificate serial.
|
|
Serial string `db:"serial"`
|
|
|
|
// createdAt: The date the CRL was signed.
|
|
CreatedAt time.Time `db:"createdAt"`
|
|
|
|
// crl: The encoded and signed CRL.
|
|
CRL string `db:"crl"`
|
|
}
|
|
|
|
// DeniedCSR is a list of names we deny issuing.
|
|
type DeniedCSR struct {
|
|
ID int `db:"id"`
|
|
|
|
Names string `db:"names"`
|
|
}
|
|
|
|
// OCSPSigningRequest is a transfer object representing an OCSP Signing Request
|
|
type OCSPSigningRequest struct {
|
|
CertDER []byte
|
|
Status string
|
|
Reason RevocationCode
|
|
RevokedAt time.Time
|
|
}
|
|
|
|
// SignedCertificateTimestamp is the internal representation of ct.SignedCertificateTimestamp
|
|
// that is used to maintain backwards compatibility with our old CT implementation.
|
|
type SignedCertificateTimestamp struct {
|
|
ID int `db:"id"`
|
|
// The version of the protocol to which the SCT conforms
|
|
SCTVersion uint8 `db:"sctVersion"`
|
|
// the SHA-256 hash of the log's public key, calculated over
|
|
// the DER encoding of the key represented as SubjectPublicKeyInfo.
|
|
LogID string `db:"logID"`
|
|
// Timestamp (in ms since unix epoc) at which the SCT was issued
|
|
Timestamp uint64 `db:"timestamp"`
|
|
// For future extensions to the protocol
|
|
Extensions []byte `db:"extensions"`
|
|
// The Log's signature for this SCT
|
|
Signature []byte `db:"signature"`
|
|
|
|
// The serial of the certificate this SCT is for
|
|
CertificateSerial string `db:"certificateSerial"`
|
|
|
|
LockCol int64
|
|
}
|
|
|
|
// RevocationCode is used to specify a certificate revocation reason
|
|
type RevocationCode int
|
|
|
|
// RevocationReasons provides a map from reason code to string explaining the
|
|
// code
|
|
var RevocationReasons = map[RevocationCode]string{
|
|
0: "unspecified",
|
|
1: "keyCompromise",
|
|
2: "cACompromise",
|
|
3: "affiliationChanged",
|
|
4: "superseded",
|
|
5: "cessationOfOperation",
|
|
6: "certificateHold",
|
|
// 7 is unused
|
|
8: "removeFromCRL", // needed?
|
|
9: "privilegeWithdrawn",
|
|
10: "aAcompromise",
|
|
}
|
|
|
|
// FQDNSet contains the SHA256 hash of the lowercased, comma joined dNSNames
|
|
// contained in a certificate.
|
|
type FQDNSet struct {
|
|
ID int64
|
|
SetHash []byte
|
|
Serial string
|
|
Issued time.Time
|
|
Expires time.Time
|
|
}
|
|
|
|
// GPDNSAnswer represents a DNS record returned by the Google Public DNS API
|
|
type GPDNSAnswer struct {
|
|
Name string `json:"name"`
|
|
Type uint16 `json:"type"`
|
|
TTL int `json:"TTL"`
|
|
Data string `json:"data"`
|
|
}
|
|
|
|
// GPDNSAnswer represents a DNS record returned by the Google Public DNS API
|
|
type GPDNSResponse struct {
|
|
// Ignored fields
|
|
// tc
|
|
// rd
|
|
// ra
|
|
// ad
|
|
// cd
|
|
// question
|
|
// additional
|
|
// edns_client_subnet
|
|
Status int `json:"Status"`
|
|
Answer []GPDNSAnswer `json:"Answer"`
|
|
Comment string `json:"Comment"`
|
|
}
|