// Copyright 2016 ISRG. All rights reserved // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package grpc import ( "encoding/json" "net" "time" "google.golang.org/grpc/codes" "gopkg.in/square/go-jose.v2" "github.com/letsencrypt/boulder/core" corepb "github.com/letsencrypt/boulder/core/proto" "github.com/letsencrypt/boulder/probs" sapb "github.com/letsencrypt/boulder/sa/proto" vapb "github.com/letsencrypt/boulder/va/proto" ) var ErrMissingParameters = CodedError(codes.FailedPrecondition, "required RPC parameter was missing") // This file defines functions to translate between the protobuf types and the // code types. func authzMetaToPB(authz core.Authorization) (*vapb.AuthzMeta, error) { return &vapb.AuthzMeta{ Id: &authz.ID, RegID: &authz.RegistrationID, }, nil } func pbToAuthzMeta(in *vapb.AuthzMeta) (core.Authorization, error) { if in == nil || in.Id == nil || in.RegID == nil { return core.Authorization{}, ErrMissingParameters } return core.Authorization{ ID: *in.Id, RegistrationID: *in.RegID, }, nil } func jwkToString(jwk *jose.JSONWebKey) (string, error) { bytes, err := jwk.MarshalJSON() return string(bytes), err } func stringToJWK(in string) (*jose.JSONWebKey, error) { var jwk = new(jose.JSONWebKey) err := jwk.UnmarshalJSON([]byte(in)) if err != nil { return nil, err } return jwk, nil } func ProblemDetailsToPB(prob *probs.ProblemDetails) (*corepb.ProblemDetails, error) { if prob == nil { // nil problemDetails is valid return nil, nil } pt := string(prob.Type) st := int32(prob.HTTPStatus) return &corepb.ProblemDetails{ ProblemType: &pt, Detail: &prob.Detail, HttpStatus: &st, }, nil } func PBToProblemDetails(in *corepb.ProblemDetails) (*probs.ProblemDetails, error) { if in == nil { // nil problemDetails is valid return nil, nil } if in.ProblemType == nil || in.Detail == nil { return nil, ErrMissingParameters } prob := &probs.ProblemDetails{ Type: probs.ProblemType(*in.ProblemType), Detail: *in.Detail, } if in.HttpStatus != nil { prob.HTTPStatus = int(*in.HttpStatus) } return prob, nil } func ChallengeToPB(challenge core.Challenge) (*corepb.Challenge, error) { st := string(challenge.Status) prob, err := ProblemDetailsToPB(challenge.Error) if err != nil { return nil, err } recordAry := make([]*corepb.ValidationRecord, len(challenge.ValidationRecord)) for i, v := range challenge.ValidationRecord { recordAry[i], err = validationRecordToPB(v) if err != nil { return nil, err } } return &corepb.Challenge{ Id: &challenge.ID, Type: &challenge.Type, Status: &st, Token: &challenge.Token, KeyAuthorization: &challenge.ProvidedKeyAuthorization, Error: prob, Validationrecords: recordAry, }, nil } func pbToChallenge(in *corepb.Challenge) (challenge core.Challenge, err error) { if in == nil { return core.Challenge{}, ErrMissingParameters } if in.Id == nil || in.Type == nil || in.Status == nil || in.Token == nil || in.KeyAuthorization == nil { return core.Challenge{}, ErrMissingParameters } var recordAry []core.ValidationRecord if len(in.Validationrecords) > 0 { recordAry = make([]core.ValidationRecord, len(in.Validationrecords)) for i, v := range in.Validationrecords { recordAry[i], err = pbToValidationRecord(v) if err != nil { return core.Challenge{}, err } } } prob, err := PBToProblemDetails(in.Error) if err != nil { return core.Challenge{}, err } return core.Challenge{ ID: *in.Id, Type: *in.Type, Status: core.AcmeStatus(*in.Status), Token: *in.Token, ProvidedKeyAuthorization: *in.KeyAuthorization, Error: prob, ValidationRecord: recordAry, }, nil } func validationRecordToPB(record core.ValidationRecord) (*corepb.ValidationRecord, error) { addrs := make([][]byte, len(record.AddressesResolved)) addrsTried := make([][]byte, len(record.AddressesTried)) var err error for i, v := range record.AddressesResolved { addrs[i] = []byte(v) } for i, v := range record.AddressesTried { addrsTried[i] = []byte(v) } addrUsed, err := record.AddressUsed.MarshalText() if err != nil { return nil, err } return &corepb.ValidationRecord{ Hostname: &record.Hostname, Port: &record.Port, AddressesResolved: addrs, AddressUsed: addrUsed, Authorities: record.Authorities, Url: &record.URL, AddressesTried: addrsTried, }, nil } func pbToValidationRecord(in *corepb.ValidationRecord) (record core.ValidationRecord, err error) { if in == nil { return core.ValidationRecord{}, ErrMissingParameters } if in.AddressUsed == nil || in.Hostname == nil || in.Port == nil || in.Url == nil { return core.ValidationRecord{}, ErrMissingParameters } addrs := make([]net.IP, len(in.AddressesResolved)) for i, v := range in.AddressesResolved { addrs[i] = net.IP(v) } addrsTried := make([]net.IP, len(in.AddressesTried)) for i, v := range in.AddressesTried { addrsTried[i] = net.IP(v) } var addrUsed net.IP err = addrUsed.UnmarshalText(in.AddressUsed) if err != nil { return } return core.ValidationRecord{ Hostname: *in.Hostname, Port: *in.Port, AddressesResolved: addrs, AddressUsed: addrUsed, Authorities: in.Authorities, URL: *in.Url, AddressesTried: addrsTried, }, nil } func validationResultToPB(records []core.ValidationRecord, prob *probs.ProblemDetails) (*vapb.ValidationResult, error) { recordAry := make([]*corepb.ValidationRecord, len(records)) var err error for i, v := range records { recordAry[i], err = validationRecordToPB(v) if err != nil { return nil, err } } marshalledProbs, err := ProblemDetailsToPB(prob) if err != nil { return nil, err } return &vapb.ValidationResult{ Records: recordAry, Problems: marshalledProbs, }, nil } func pbToValidationResult(in *vapb.ValidationResult) ([]core.ValidationRecord, *probs.ProblemDetails, error) { if in == nil { return nil, nil, ErrMissingParameters } recordAry := make([]core.ValidationRecord, len(in.Records)) var err error for i, v := range in.Records { recordAry[i], err = pbToValidationRecord(v) if err != nil { return nil, nil, err } } prob, err := PBToProblemDetails(in.Problems) if err != nil { return nil, nil, err } return recordAry, prob, nil } func performValidationReqToArgs(in *vapb.PerformValidationRequest) (domain string, challenge core.Challenge, authz core.Authorization, err error) { if in == nil { err = ErrMissingParameters return } if in.Domain == nil { err = ErrMissingParameters return } domain = *in.Domain challenge, err = pbToChallenge(in.Challenge) if err != nil { return } authz, err = pbToAuthzMeta(in.Authz) if err != nil { return } return domain, challenge, authz, nil } func argsToPerformValidationRequest(domain string, challenge core.Challenge, authz core.Authorization) (*vapb.PerformValidationRequest, error) { pbChall, err := ChallengeToPB(challenge) if err != nil { return nil, err } authzMeta, err := authzMetaToPB(authz) if err != nil { return nil, err } return &vapb.PerformValidationRequest{ Domain: &domain, Challenge: pbChall, Authz: authzMeta, }, nil } func registrationToPB(reg core.Registration) (*corepb.Registration, error) { keyBytes, err := reg.Key.MarshalJSON() if err != nil { return nil, err } ipBytes, err := reg.InitialIP.MarshalText() if err != nil { return nil, err } createdAt := reg.CreatedAt.UnixNano() status := string(reg.Status) var contacts []string // Since the default value of corepb.Registration.Contact is a slice // we need a indicator as to if the value is actually important on // the other side (pb -> reg). contactsPresent := reg.Contact != nil if reg.Contact != nil { contacts = *reg.Contact } return &corepb.Registration{ Id: ®.ID, Key: keyBytes, Contact: contacts, ContactsPresent: &contactsPresent, Agreement: ®.Agreement, InitialIP: ipBytes, CreatedAt: &createdAt, Status: &status, }, nil } func pbToRegistration(pb *corepb.Registration) (core.Registration, error) { var key jose.JSONWebKey err := key.UnmarshalJSON(pb.Key) if err != nil { return core.Registration{}, err } var initialIP net.IP err = initialIP.UnmarshalText(pb.InitialIP) if err != nil { return core.Registration{}, err } var contacts *[]string if *pb.ContactsPresent { if len(pb.Contact) != 0 { contacts = &pb.Contact } else { // When gRPC creates an empty slice it is actually a nil slice. Since // certain things boulder uses, like encoding/json, differentiate between // these we need to de-nil these slices. Without this we are unable to // properly do registration updates as contacts would always be removed // as we use the difference between a nil and empty slice in ra.mergeUpdate. empty := []string{} contacts = &empty } } return core.Registration{ ID: *pb.Id, Key: &key, Contact: contacts, Agreement: *pb.Agreement, InitialIP: initialIP, CreatedAt: time.Unix(0, *pb.CreatedAt), Status: core.AcmeStatus(*pb.Status), }, nil } func AuthzToPB(authz core.Authorization) (*corepb.Authorization, error) { challs := make([]*corepb.Challenge, len(authz.Challenges)) for i, c := range authz.Challenges { pbChall, err := ChallengeToPB(c) if err != nil { return nil, err } challs[i] = pbChall } comboBytes, err := json.Marshal(authz.Combinations) if err != nil { return nil, err } status := string(authz.Status) var expires int64 if authz.Expires != nil { expires = authz.Expires.UnixNano() } return &corepb.Authorization{ Id: &authz.ID, Identifier: &authz.Identifier.Value, RegistrationID: &authz.RegistrationID, Status: &status, Expires: &expires, Challenges: challs, Combinations: comboBytes, }, nil } func PBToAuthz(pb *corepb.Authorization) (core.Authorization, error) { challs := make([]core.Challenge, len(pb.Challenges)) for i, c := range pb.Challenges { chall, err := pbToChallenge(c) if err != nil { return core.Authorization{}, err } challs[i] = chall } var combos [][]int err := json.Unmarshal(pb.Combinations, &combos) if err != nil { return core.Authorization{}, err } expires := time.Unix(0, *pb.Expires) authz := core.Authorization{ Identifier: core.AcmeIdentifier{Type: core.IdentifierDNS, Value: *pb.Identifier}, RegistrationID: *pb.RegistrationID, Status: core.AcmeStatus(*pb.Status), Expires: &expires, Challenges: challs, Combinations: combos, } if pb.Id != nil { authz.ID = *pb.Id } return authz, nil } func registrationValid(reg *corepb.Registration) bool { return !(reg.Id == nil || reg.Key == nil || reg.Agreement == nil || reg.InitialIP == nil || reg.CreatedAt == nil || reg.Status == nil || reg.ContactsPresent == nil) } // orderValid checks that a corepb.Order is valid. In addition to the checks // from `newOrderValid` it ensures the order ID, the BeganProcessing fields // and the Created field are not nil. func orderValid(order *corepb.Order) bool { return order.Id != nil && order.BeganProcessing != nil && order.Created != nil && newOrderValid(order) } // newOrderValid checks that a corepb.Order is valid. It allows for a nil // `order.Id` because the order has not been assigned an ID yet when it is being // created initially. It allows `order.BeganProcessing` to be nil because // `sa.NewOrder` explicitly sets it to the default value. It allows // `order.Created` to be nil because the SA populates this. It also allows // `order.CertificateSerial` to be nil such that it can be used in places where // the order has not been finalized yet. func newOrderValid(order *corepb.Order) bool { return !(order.RegistrationID == nil || order.Expires == nil || order.Authorizations == nil || order.Names == nil) } func authorizationValid(authz *corepb.Authorization) bool { return !(authz.Id == nil || authz.Identifier == nil || authz.RegistrationID == nil || authz.Status == nil || authz.Expires == nil) } func sctToPB(sct core.SignedCertificateTimestamp) *sapb.SignedCertificateTimestamp { id := int64(sct.ID) version := int64(sct.SCTVersion) timestamp := int64(sct.Timestamp) return &sapb.SignedCertificateTimestamp{ Id: &id, SctVersion: &version, LogID: &sct.LogID, Timestamp: ×tamp, Extensions: sct.Extensions, Signature: sct.Signature, CertificateSerial: &sct.CertificateSerial, } } func pbToSCT(pb *sapb.SignedCertificateTimestamp) core.SignedCertificateTimestamp { return core.SignedCertificateTimestamp{ ID: int(*pb.Id), SCTVersion: uint8(*pb.SctVersion), LogID: *pb.LogID, Timestamp: uint64(*pb.Timestamp), Extensions: pb.Extensions, Signature: pb.Signature, CertificateSerial: *pb.CertificateSerial, } } func sctValid(sct *sapb.SignedCertificateTimestamp) bool { return !(sct.Id == nil || sct.SctVersion == nil || sct.LogID == nil || sct.Timestamp == nil || sct.Signature == nil || sct.CertificateSerial == nil) } func certToPB(cert core.Certificate) *corepb.Certificate { issued, expires := cert.Issued.UnixNano(), cert.Expires.UnixNano() return &corepb.Certificate{ RegistrationID: &cert.RegistrationID, Serial: &cert.Serial, Digest: &cert.Digest, Der: cert.DER, Issued: &issued, Expires: &expires, } } func pbToCert(pb *corepb.Certificate) (core.Certificate, error) { if pb == nil || pb.RegistrationID == nil || pb.Serial == nil || pb.Digest == nil || pb.Der == nil || pb.Issued == nil || pb.Expires == nil { return core.Certificate{}, errIncompleteResponse } return core.Certificate{ RegistrationID: *pb.RegistrationID, Serial: *pb.Serial, Digest: *pb.Digest, DER: pb.Der, Issued: time.Unix(0, *pb.Issued), Expires: time.Unix(0, *pb.Expires), }, nil }