...

Source file src/github.com/letsencrypt/boulder/grpc/pb-marshalling.go

Documentation: github.com/letsencrypt/boulder/grpc

     1  // Copyright 2016 ISRG.  All rights reserved
     2  // This Source Code Form is subject to the terms of the Mozilla Public
     3  // License, v. 2.0. If a copy of the MPL was not distributed with this
     4  // file, You can obtain one at http://mozilla.org/MPL/2.0/.
     5  
     6  package grpc
     7  
     8  import (
     9  	"fmt"
    10  	"net"
    11  	"time"
    12  
    13  	"google.golang.org/grpc/codes"
    14  	"google.golang.org/protobuf/types/known/timestamppb"
    15  	"gopkg.in/go-jose/go-jose.v2"
    16  
    17  	"github.com/letsencrypt/boulder/core"
    18  	corepb "github.com/letsencrypt/boulder/core/proto"
    19  	"github.com/letsencrypt/boulder/identifier"
    20  	"github.com/letsencrypt/boulder/probs"
    21  	"github.com/letsencrypt/boulder/revocation"
    22  	sapb "github.com/letsencrypt/boulder/sa/proto"
    23  	vapb "github.com/letsencrypt/boulder/va/proto"
    24  )
    25  
    26  var ErrMissingParameters = CodedError(codes.FailedPrecondition, "required RPC parameter was missing")
    27  
    28  // This file defines functions to translate between the protobuf types and the
    29  // code types.
    30  
    31  func ProblemDetailsToPB(prob *probs.ProblemDetails) (*corepb.ProblemDetails, error) {
    32  	if prob == nil {
    33  		// nil problemDetails is valid
    34  		return nil, nil
    35  	}
    36  	return &corepb.ProblemDetails{
    37  		ProblemType: string(prob.Type),
    38  		Detail:      prob.Detail,
    39  		HttpStatus:  int32(prob.HTTPStatus),
    40  	}, nil
    41  }
    42  
    43  func PBToProblemDetails(in *corepb.ProblemDetails) (*probs.ProblemDetails, error) {
    44  	if in == nil {
    45  		// nil problemDetails is valid
    46  		return nil, nil
    47  	}
    48  	if in.ProblemType == "" || in.Detail == "" {
    49  		return nil, ErrMissingParameters
    50  	}
    51  	prob := &probs.ProblemDetails{
    52  		Type:   probs.ProblemType(in.ProblemType),
    53  		Detail: in.Detail,
    54  	}
    55  	if in.HttpStatus != 0 {
    56  		prob.HTTPStatus = int(in.HttpStatus)
    57  	}
    58  	return prob, nil
    59  }
    60  
    61  func ChallengeToPB(challenge core.Challenge) (*corepb.Challenge, error) {
    62  	prob, err := ProblemDetailsToPB(challenge.Error)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  	recordAry := make([]*corepb.ValidationRecord, len(challenge.ValidationRecord))
    67  	for i, v := range challenge.ValidationRecord {
    68  		recordAry[i], err = ValidationRecordToPB(v)
    69  		if err != nil {
    70  			return nil, err
    71  		}
    72  	}
    73  
    74  	var validatedInt int64 = 0
    75  	validatedTS := timestamppb.New(time.Time{})
    76  	if challenge.Validated != nil {
    77  		validatedInt = challenge.Validated.UTC().UnixNano()
    78  		validatedTS = timestamppb.New(challenge.Validated.UTC())
    79  	}
    80  
    81  	if !validatedTS.IsValid() {
    82  		return nil, fmt.Errorf("error creating *timestamppb.Timestamp for *corepb.Challenge object")
    83  	}
    84  
    85  	return &corepb.Challenge{
    86  		Type:              string(challenge.Type),
    87  		Status:            string(challenge.Status),
    88  		Token:             challenge.Token,
    89  		KeyAuthorization:  challenge.ProvidedKeyAuthorization,
    90  		Error:             prob,
    91  		Validationrecords: recordAry,
    92  		ValidatedNS:       validatedInt,
    93  		Validated:         validatedTS,
    94  	}, nil
    95  }
    96  
    97  func PBToChallenge(in *corepb.Challenge) (challenge core.Challenge, err error) {
    98  	if in == nil {
    99  		return core.Challenge{}, ErrMissingParameters
   100  	}
   101  	if in.Type == "" || in.Status == "" || in.Token == "" {
   102  		return core.Challenge{}, ErrMissingParameters
   103  	}
   104  	var recordAry []core.ValidationRecord
   105  	if len(in.Validationrecords) > 0 {
   106  		recordAry = make([]core.ValidationRecord, len(in.Validationrecords))
   107  		for i, v := range in.Validationrecords {
   108  			recordAry[i], err = PBToValidationRecord(v)
   109  			if err != nil {
   110  				return core.Challenge{}, err
   111  			}
   112  		}
   113  	}
   114  	prob, err := PBToProblemDetails(in.Error)
   115  	if err != nil {
   116  		return core.Challenge{}, err
   117  	}
   118  	var validated *time.Time
   119  	if in.ValidatedNS != 0 {
   120  		val := time.Unix(0, in.ValidatedNS).UTC()
   121  		validated = &val
   122  	}
   123  	ch := core.Challenge{
   124  		Type:             core.AcmeChallenge(in.Type),
   125  		Status:           core.AcmeStatus(in.Status),
   126  		Token:            in.Token,
   127  		Error:            prob,
   128  		ValidationRecord: recordAry,
   129  		Validated:        validated,
   130  	}
   131  	if in.KeyAuthorization != "" {
   132  		ch.ProvidedKeyAuthorization = in.KeyAuthorization
   133  	}
   134  	return ch, nil
   135  }
   136  
   137  func ValidationRecordToPB(record core.ValidationRecord) (*corepb.ValidationRecord, error) {
   138  	addrs := make([][]byte, len(record.AddressesResolved))
   139  	addrsTried := make([][]byte, len(record.AddressesTried))
   140  	var err error
   141  	for i, v := range record.AddressesResolved {
   142  		addrs[i] = []byte(v)
   143  	}
   144  	for i, v := range record.AddressesTried {
   145  		addrsTried[i] = []byte(v)
   146  	}
   147  	addrUsed, err := record.AddressUsed.MarshalText()
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  	return &corepb.ValidationRecord{
   152  		Hostname:          record.Hostname,
   153  		Port:              record.Port,
   154  		AddressesResolved: addrs,
   155  		AddressUsed:       addrUsed,
   156  		Url:               record.URL,
   157  		AddressesTried:    addrsTried,
   158  	}, nil
   159  }
   160  
   161  func PBToValidationRecord(in *corepb.ValidationRecord) (record core.ValidationRecord, err error) {
   162  	if in == nil {
   163  		return core.ValidationRecord{}, ErrMissingParameters
   164  	}
   165  	addrs := make([]net.IP, len(in.AddressesResolved))
   166  	for i, v := range in.AddressesResolved {
   167  		addrs[i] = net.IP(v)
   168  	}
   169  	addrsTried := make([]net.IP, len(in.AddressesTried))
   170  	for i, v := range in.AddressesTried {
   171  		addrsTried[i] = net.IP(v)
   172  	}
   173  	var addrUsed net.IP
   174  	err = addrUsed.UnmarshalText(in.AddressUsed)
   175  	if err != nil {
   176  		return
   177  	}
   178  	return core.ValidationRecord{
   179  		Hostname:          in.Hostname,
   180  		Port:              in.Port,
   181  		AddressesResolved: addrs,
   182  		AddressUsed:       addrUsed,
   183  		URL:               in.Url,
   184  		AddressesTried:    addrsTried,
   185  	}, nil
   186  }
   187  
   188  func ValidationResultToPB(records []core.ValidationRecord, prob *probs.ProblemDetails) (*vapb.ValidationResult, error) {
   189  	recordAry := make([]*corepb.ValidationRecord, len(records))
   190  	var err error
   191  	for i, v := range records {
   192  		recordAry[i], err = ValidationRecordToPB(v)
   193  		if err != nil {
   194  			return nil, err
   195  		}
   196  	}
   197  	marshalledProbs, err := ProblemDetailsToPB(prob)
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  	return &vapb.ValidationResult{
   202  		Records:  recordAry,
   203  		Problems: marshalledProbs,
   204  	}, nil
   205  }
   206  
   207  func pbToValidationResult(in *vapb.ValidationResult) ([]core.ValidationRecord, *probs.ProblemDetails, error) {
   208  	if in == nil {
   209  		return nil, nil, ErrMissingParameters
   210  	}
   211  	recordAry := make([]core.ValidationRecord, len(in.Records))
   212  	var err error
   213  	for i, v := range in.Records {
   214  		recordAry[i], err = PBToValidationRecord(v)
   215  		if err != nil {
   216  			return nil, nil, err
   217  		}
   218  	}
   219  	prob, err := PBToProblemDetails(in.Problems)
   220  	if err != nil {
   221  		return nil, nil, err
   222  	}
   223  	return recordAry, prob, nil
   224  }
   225  
   226  func RegistrationToPB(reg core.Registration) (*corepb.Registration, error) {
   227  	keyBytes, err := reg.Key.MarshalJSON()
   228  	if err != nil {
   229  		return nil, err
   230  	}
   231  	ipBytes, err := reg.InitialIP.MarshalText()
   232  	if err != nil {
   233  		return nil, err
   234  	}
   235  	var contacts []string
   236  	// Since the default value of corepb.Registration.Contact is a slice
   237  	// we need a indicator as to if the value is actually important on
   238  	// the other side (pb -> reg).
   239  	contactsPresent := reg.Contact != nil
   240  	if reg.Contact != nil {
   241  		contacts = *reg.Contact
   242  	}
   243  	var createdAtInt int64 = 0
   244  	createdAtTS := timestamppb.New(time.Time{})
   245  	if reg.CreatedAt != nil {
   246  		createdAtInt = reg.CreatedAt.UTC().UnixNano()
   247  		createdAtTS = timestamppb.New(reg.CreatedAt.UTC())
   248  	}
   249  	if !createdAtTS.IsValid() {
   250  		return nil, fmt.Errorf("error creating *timestamppb.Timestamp for *corepb.Authorization object")
   251  	}
   252  
   253  	return &corepb.Registration{
   254  		Id:              reg.ID,
   255  		Key:             keyBytes,
   256  		Contact:         contacts,
   257  		ContactsPresent: contactsPresent,
   258  		Agreement:       reg.Agreement,
   259  		InitialIP:       ipBytes,
   260  		CreatedAtNS:     createdAtInt,
   261  		CreatedAt:       createdAtTS,
   262  		Status:          string(reg.Status),
   263  	}, nil
   264  }
   265  
   266  func PbToRegistration(pb *corepb.Registration) (core.Registration, error) {
   267  	var key jose.JSONWebKey
   268  	err := key.UnmarshalJSON(pb.Key)
   269  	if err != nil {
   270  		return core.Registration{}, err
   271  	}
   272  	var initialIP net.IP
   273  	err = initialIP.UnmarshalText(pb.InitialIP)
   274  	if err != nil {
   275  		return core.Registration{}, err
   276  	}
   277  	var createdAt *time.Time
   278  	if pb.CreatedAtNS != 0 {
   279  		c := time.Unix(0, pb.CreatedAtNS).UTC()
   280  		createdAt = &c
   281  	}
   282  	var contacts *[]string
   283  	if pb.ContactsPresent {
   284  		if len(pb.Contact) != 0 {
   285  			contacts = &pb.Contact
   286  		} else {
   287  			// When gRPC creates an empty slice it is actually a nil slice. Since
   288  			// certain things boulder uses, like encoding/json, differentiate between
   289  			// these we need to de-nil these slices. Without this we are unable to
   290  			// properly do registration updates as contacts would always be removed
   291  			// as we use the difference between a nil and empty slice in ra.mergeUpdate.
   292  			empty := []string{}
   293  			contacts = &empty
   294  		}
   295  	}
   296  	return core.Registration{
   297  		ID:        pb.Id,
   298  		Key:       &key,
   299  		Contact:   contacts,
   300  		Agreement: pb.Agreement,
   301  		InitialIP: initialIP,
   302  		CreatedAt: createdAt,
   303  		Status:    core.AcmeStatus(pb.Status),
   304  	}, nil
   305  }
   306  
   307  func AuthzToPB(authz core.Authorization) (*corepb.Authorization, error) {
   308  	challs := make([]*corepb.Challenge, len(authz.Challenges))
   309  	for i, c := range authz.Challenges {
   310  		pbChall, err := ChallengeToPB(c)
   311  		if err != nil {
   312  			return nil, err
   313  		}
   314  		challs[i] = pbChall
   315  	}
   316  	var expiresInt int64 = 0
   317  	expiresTS := timestamppb.New(time.Time{})
   318  	if authz.Expires != nil {
   319  		expiresInt = authz.Expires.UTC().UnixNano()
   320  		expiresTS = timestamppb.New(authz.Expires.UTC())
   321  	}
   322  	if !expiresTS.IsValid() {
   323  		return nil, fmt.Errorf("error creating *timestamppb.Timestamp for *corepb.Authorization object")
   324  	}
   325  
   326  	return &corepb.Authorization{
   327  		Id:             authz.ID,
   328  		Identifier:     authz.Identifier.Value,
   329  		RegistrationID: authz.RegistrationID,
   330  		Status:         string(authz.Status),
   331  		ExpiresNS:      expiresInt,
   332  		Expires:        expiresTS,
   333  		Challenges:     challs,
   334  	}, nil
   335  }
   336  
   337  func PBToAuthz(pb *corepb.Authorization) (core.Authorization, error) {
   338  	challs := make([]core.Challenge, len(pb.Challenges))
   339  	for i, c := range pb.Challenges {
   340  		chall, err := PBToChallenge(c)
   341  		if err != nil {
   342  			return core.Authorization{}, err
   343  		}
   344  		challs[i] = chall
   345  	}
   346  	var expires *time.Time
   347  	if pb.ExpiresNS != 0 {
   348  		c := time.Unix(0, pb.ExpiresNS).UTC()
   349  		expires = &c
   350  	}
   351  	authz := core.Authorization{
   352  		ID:             pb.Id,
   353  		Identifier:     identifier.ACMEIdentifier{Type: identifier.DNS, Value: pb.Identifier},
   354  		RegistrationID: pb.RegistrationID,
   355  		Status:         core.AcmeStatus(pb.Status),
   356  		Expires:        expires,
   357  		Challenges:     challs,
   358  	}
   359  	return authz, nil
   360  }
   361  
   362  // orderValid checks that a corepb.Order is valid. In addition to the checks
   363  // from `newOrderValid` it ensures the order ID and the Created fields are not
   364  // the zero value.
   365  func orderValid(order *corepb.Order) bool {
   366  	return order.Id != 0 && order.CreatedNS != 0 && newOrderValid(order)
   367  }
   368  
   369  // newOrderValid checks that a corepb.Order is valid. It allows for a nil
   370  // `order.Id` because the order has not been assigned an ID yet when it is being
   371  // created initially. It allows `order.BeganProcessing` to be nil because
   372  // `sa.NewOrder` explicitly sets it to the default value. It allows
   373  // `order.Created` to be nil because the SA populates this. It also allows
   374  // `order.CertificateSerial` to be nil such that it can be used in places where
   375  // the order has not been finalized yet.
   376  func newOrderValid(order *corepb.Order) bool {
   377  	return !(order.RegistrationID == 0 || order.ExpiresNS == 0 || len(order.Names) == 0)
   378  }
   379  
   380  func CertToPB(cert core.Certificate) *corepb.Certificate {
   381  	return &corepb.Certificate{
   382  		RegistrationID: cert.RegistrationID,
   383  		Serial:         cert.Serial,
   384  		Digest:         cert.Digest,
   385  		Der:            cert.DER,
   386  		IssuedNS:       cert.Issued.UnixNano(),
   387  		Issued:         timestamppb.New(cert.Issued),
   388  		ExpiresNS:      cert.Expires.UnixNano(),
   389  		Expires:        timestamppb.New(cert.Expires),
   390  	}
   391  }
   392  
   393  func PBToCert(pb *corepb.Certificate) core.Certificate {
   394  	return core.Certificate{
   395  		RegistrationID: pb.RegistrationID,
   396  		Serial:         pb.Serial,
   397  		Digest:         pb.Digest,
   398  		DER:            pb.Der,
   399  		Issued:         time.Unix(0, pb.IssuedNS),
   400  		Expires:        time.Unix(0, pb.ExpiresNS),
   401  	}
   402  }
   403  
   404  func CertStatusToPB(certStatus core.CertificateStatus) *corepb.CertificateStatus {
   405  	return &corepb.CertificateStatus{
   406  		Serial:                  certStatus.Serial,
   407  		Status:                  string(certStatus.Status),
   408  		OcspLastUpdatedNS:       certStatus.OCSPLastUpdated.UnixNano(),
   409  		OcspLastUpdated:         timestamppb.New(certStatus.OCSPLastUpdated),
   410  		RevokedDateNS:           certStatus.RevokedDate.UnixNano(),
   411  		RevokedDate:             timestamppb.New(certStatus.RevokedDate),
   412  		RevokedReason:           int64(certStatus.RevokedReason),
   413  		LastExpirationNagSentNS: certStatus.LastExpirationNagSent.UnixNano(),
   414  		LastExpirationNagSent:   timestamppb.New(certStatus.LastExpirationNagSent),
   415  		NotAfterNS:              certStatus.NotAfter.UnixNano(),
   416  		NotAfter:                timestamppb.New(certStatus.NotAfter),
   417  		IsExpired:               certStatus.IsExpired,
   418  		IssuerID:                certStatus.IssuerNameID,
   419  	}
   420  }
   421  
   422  func PBToCertStatus(pb *corepb.CertificateStatus) core.CertificateStatus {
   423  	return core.CertificateStatus{
   424  		Serial:                pb.Serial,
   425  		Status:                core.OCSPStatus(pb.Status),
   426  		OCSPLastUpdated:       time.Unix(0, pb.OcspLastUpdatedNS),
   427  		RevokedDate:           time.Unix(0, pb.RevokedDateNS),
   428  		RevokedReason:         revocation.Reason(pb.RevokedReason),
   429  		LastExpirationNagSent: time.Unix(0, pb.LastExpirationNagSentNS),
   430  		NotAfter:              time.Unix(0, pb.NotAfterNS),
   431  		IsExpired:             pb.IsExpired,
   432  		IssuerNameID:          pb.IssuerID,
   433  	}
   434  }
   435  
   436  // PBToAuthzMap converts a protobuf map of domains mapped to protobuf authorizations to a
   437  // golang map[string]*core.Authorization.
   438  func PBToAuthzMap(pb *sapb.Authorizations) (map[string]*core.Authorization, error) {
   439  	m := make(map[string]*core.Authorization, len(pb.Authz))
   440  	for _, v := range pb.Authz {
   441  		authz, err := PBToAuthz(v.Authz)
   442  		if err != nil {
   443  			return nil, err
   444  		}
   445  		m[v.Domain] = &authz
   446  	}
   447  	return m, nil
   448  }
   449  

View as plain text