github.com/letsencrypt/boulder@v0.20251208.0/grpc/pb-marshalling.go (about)

     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/netip"
    11  	"time"
    12  
    13  	"github.com/go-jose/go-jose/v4"
    14  	"google.golang.org/grpc/codes"
    15  	"google.golang.org/protobuf/types/known/timestamppb"
    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  	sapb "github.com/letsencrypt/boulder/sa/proto"
    22  	vapb "github.com/letsencrypt/boulder/va/proto"
    23  )
    24  
    25  var ErrMissingParameters = CodedError(codes.FailedPrecondition, "required RPC parameter was missing")
    26  var ErrInvalidParameters = CodedError(codes.InvalidArgument, "RPC parameter was invalid")
    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), //nolint: gosec // HTTP status codes are guaranteed to be small, no risk of overflow.
    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 validated *timestamppb.Timestamp
    75  	if challenge.Validated != nil {
    76  		validated = timestamppb.New(challenge.Validated.UTC())
    77  		if !validated.IsValid() {
    78  			return nil, fmt.Errorf("error creating *timestamppb.Timestamp for *corepb.Challenge object")
    79  		}
    80  	}
    81  
    82  	return &corepb.Challenge{
    83  		Type:              string(challenge.Type),
    84  		Status:            string(challenge.Status),
    85  		Token:             challenge.Token,
    86  		Error:             prob,
    87  		Validationrecords: recordAry,
    88  		Validated:         validated,
    89  	}, nil
    90  }
    91  
    92  func PBToChallenge(in *corepb.Challenge) (challenge core.Challenge, err error) {
    93  	if in == nil {
    94  		return core.Challenge{}, ErrMissingParameters
    95  	}
    96  	if in.Type == "" || in.Status == "" || in.Token == "" {
    97  		return core.Challenge{}, ErrMissingParameters
    98  	}
    99  	var recordAry []core.ValidationRecord
   100  	if len(in.Validationrecords) > 0 {
   101  		recordAry = make([]core.ValidationRecord, len(in.Validationrecords))
   102  		for i, v := range in.Validationrecords {
   103  			recordAry[i], err = PBToValidationRecord(v)
   104  			if err != nil {
   105  				return core.Challenge{}, err
   106  			}
   107  		}
   108  	}
   109  	prob, err := PBToProblemDetails(in.Error)
   110  	if err != nil {
   111  		return core.Challenge{}, err
   112  	}
   113  	var validated *time.Time
   114  	if !core.IsAnyNilOrZero(in.Validated) {
   115  		val := in.Validated.AsTime()
   116  		validated = &val
   117  	}
   118  	ch := core.Challenge{
   119  		Type:             core.AcmeChallenge(in.Type),
   120  		Status:           core.AcmeStatus(in.Status),
   121  		Token:            in.Token,
   122  		Error:            prob,
   123  		ValidationRecord: recordAry,
   124  		Validated:        validated,
   125  	}
   126  	return ch, nil
   127  }
   128  
   129  func ValidationRecordToPB(record core.ValidationRecord) (*corepb.ValidationRecord, error) {
   130  	addrs := make([][]byte, len(record.AddressesResolved))
   131  	addrsTried := make([][]byte, len(record.AddressesTried))
   132  	var err error
   133  	for i, v := range record.AddressesResolved {
   134  		addrs[i] = v.AsSlice()
   135  	}
   136  	for i, v := range record.AddressesTried {
   137  		addrsTried[i] = v.AsSlice()
   138  	}
   139  	addrUsed, err := record.AddressUsed.MarshalText()
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  	return &corepb.ValidationRecord{
   144  		Hostname:          record.Hostname,
   145  		Port:              record.Port,
   146  		AddressesResolved: addrs,
   147  		AddressUsed:       addrUsed,
   148  		Url:               record.URL,
   149  		AddressesTried:    addrsTried,
   150  		ResolverAddrs:     record.ResolverAddrs,
   151  	}, nil
   152  }
   153  
   154  func PBToValidationRecord(in *corepb.ValidationRecord) (record core.ValidationRecord, err error) {
   155  	if in == nil {
   156  		return core.ValidationRecord{}, ErrMissingParameters
   157  	}
   158  	addrs := make([]netip.Addr, len(in.AddressesResolved))
   159  	for i, v := range in.AddressesResolved {
   160  		netIP, ok := netip.AddrFromSlice(v)
   161  		if !ok {
   162  			return core.ValidationRecord{}, ErrInvalidParameters
   163  		}
   164  		addrs[i] = netIP
   165  	}
   166  	addrsTried := make([]netip.Addr, len(in.AddressesTried))
   167  	for i, v := range in.AddressesTried {
   168  		netIP, ok := netip.AddrFromSlice(v)
   169  		if !ok {
   170  			return core.ValidationRecord{}, ErrInvalidParameters
   171  		}
   172  		addrsTried[i] = netIP
   173  	}
   174  	var addrUsed netip.Addr
   175  	err = addrUsed.UnmarshalText(in.AddressUsed)
   176  	if err != nil {
   177  		return
   178  	}
   179  	return core.ValidationRecord{
   180  		Hostname:          in.Hostname,
   181  		Port:              in.Port,
   182  		AddressesResolved: addrs,
   183  		AddressUsed:       addrUsed,
   184  		URL:               in.Url,
   185  		AddressesTried:    addrsTried,
   186  		ResolverAddrs:     in.ResolverAddrs,
   187  	}, nil
   188  }
   189  
   190  func ValidationResultToPB(records []core.ValidationRecord, prob *probs.ProblemDetails, perspective, rir string) (*vapb.ValidationResult, error) {
   191  	recordAry := make([]*corepb.ValidationRecord, len(records))
   192  	var err error
   193  	for i, v := range records {
   194  		recordAry[i], err = ValidationRecordToPB(v)
   195  		if err != nil {
   196  			return nil, err
   197  		}
   198  	}
   199  	marshalledProb, err := ProblemDetailsToPB(prob)
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  	return &vapb.ValidationResult{
   204  		Records:     recordAry,
   205  		Problem:     marshalledProb,
   206  		Perspective: perspective,
   207  		Rir:         rir,
   208  	}, nil
   209  }
   210  
   211  func pbToValidationResult(in *vapb.ValidationResult) ([]core.ValidationRecord, *probs.ProblemDetails, error) {
   212  	if in == nil {
   213  		return nil, nil, ErrMissingParameters
   214  	}
   215  	recordAry := make([]core.ValidationRecord, len(in.Records))
   216  	var err error
   217  	for i, v := range in.Records {
   218  		recordAry[i], err = PBToValidationRecord(v)
   219  		if err != nil {
   220  			return nil, nil, err
   221  		}
   222  	}
   223  	prob, err := PBToProblemDetails(in.Problem)
   224  	if err != nil {
   225  		return nil, nil, err
   226  	}
   227  	return recordAry, prob, nil
   228  }
   229  
   230  func CAAResultToPB(prob *probs.ProblemDetails, perspective, rir string) (*vapb.IsCAAValidResponse, error) {
   231  	marshalledProb, err := ProblemDetailsToPB(prob)
   232  	if err != nil {
   233  		return nil, err
   234  	}
   235  	return &vapb.IsCAAValidResponse{
   236  		Problem:     marshalledProb,
   237  		Perspective: perspective,
   238  		Rir:         rir,
   239  	}, nil
   240  }
   241  
   242  func RegistrationToPB(reg core.Registration) (*corepb.Registration, error) {
   243  	keyBytes, err := reg.Key.MarshalJSON()
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  	var createdAt *timestamppb.Timestamp
   248  	if reg.CreatedAt != nil {
   249  		createdAt = timestamppb.New(reg.CreatedAt.UTC())
   250  		if !createdAt.IsValid() {
   251  			return nil, fmt.Errorf("error creating *timestamppb.Timestamp for *corepb.Authorization object")
   252  		}
   253  	}
   254  
   255  	return &corepb.Registration{
   256  		Id:        reg.ID,
   257  		Key:       keyBytes,
   258  		Agreement: reg.Agreement,
   259  		CreatedAt: createdAt,
   260  		Status:    string(reg.Status),
   261  	}, nil
   262  }
   263  
   264  func PbToRegistration(pb *corepb.Registration) (core.Registration, error) {
   265  	var key jose.JSONWebKey
   266  	err := key.UnmarshalJSON(pb.Key)
   267  	if err != nil {
   268  		return core.Registration{}, err
   269  	}
   270  	var createdAt *time.Time
   271  	if !core.IsAnyNilOrZero(pb.CreatedAt) {
   272  		c := pb.CreatedAt.AsTime()
   273  		createdAt = &c
   274  	}
   275  	return core.Registration{
   276  		ID:        pb.Id,
   277  		Key:       &key,
   278  		Agreement: pb.Agreement,
   279  		CreatedAt: createdAt,
   280  		Status:    core.AcmeStatus(pb.Status),
   281  	}, nil
   282  }
   283  
   284  func AuthzToPB(authz core.Authorization) (*corepb.Authorization, error) {
   285  	challs := make([]*corepb.Challenge, len(authz.Challenges))
   286  	for i, c := range authz.Challenges {
   287  		pbChall, err := ChallengeToPB(c)
   288  		if err != nil {
   289  			return nil, err
   290  		}
   291  		challs[i] = pbChall
   292  	}
   293  	var expires *timestamppb.Timestamp
   294  	if authz.Expires != nil {
   295  		expires = timestamppb.New(authz.Expires.UTC())
   296  		if !expires.IsValid() {
   297  			return nil, fmt.Errorf("error creating *timestamppb.Timestamp for *corepb.Authorization object")
   298  		}
   299  	}
   300  
   301  	return &corepb.Authorization{
   302  		Id:                     authz.ID,
   303  		Identifier:             authz.Identifier.ToProto(),
   304  		RegistrationID:         authz.RegistrationID,
   305  		Status:                 string(authz.Status),
   306  		Expires:                expires,
   307  		Challenges:             challs,
   308  		CertificateProfileName: authz.CertificateProfileName,
   309  	}, nil
   310  }
   311  
   312  func PBToAuthz(pb *corepb.Authorization) (core.Authorization, error) {
   313  	challs := make([]core.Challenge, len(pb.Challenges))
   314  	for i, c := range pb.Challenges {
   315  		chall, err := PBToChallenge(c)
   316  		if err != nil {
   317  			return core.Authorization{}, err
   318  		}
   319  		challs[i] = chall
   320  	}
   321  	var expires *time.Time
   322  	if !core.IsAnyNilOrZero(pb.Expires) {
   323  		c := pb.Expires.AsTime()
   324  		expires = &c
   325  	}
   326  	authz := core.Authorization{
   327  		ID:                     pb.Id,
   328  		Identifier:             identifier.FromProto(pb.Identifier),
   329  		RegistrationID:         pb.RegistrationID,
   330  		Status:                 core.AcmeStatus(pb.Status),
   331  		Expires:                expires,
   332  		Challenges:             challs,
   333  		CertificateProfileName: pb.CertificateProfileName,
   334  	}
   335  	return authz, nil
   336  }
   337  
   338  // orderValid checks that a corepb.Order is valid. In addition to the checks
   339  // from `newOrderValid` it ensures the order ID and the Created fields are not
   340  // the zero value.
   341  func orderValid(order *corepb.Order) bool {
   342  	return order.Id != 0 && order.Created != nil && newOrderValid(order)
   343  }
   344  
   345  // newOrderValid checks that a corepb.Order is valid. It allows for a nil
   346  // `order.Id` because the order has not been assigned an ID yet when it is being
   347  // created initially. It allows `order.BeganProcessing` to be nil because
   348  // `sa.NewOrder` explicitly sets it to the default value. It allows
   349  // `order.Created` to be nil because the SA populates this. It also allows
   350  // `order.CertificateSerial` to be nil such that it can be used in places where
   351  // the order has not been finalized yet.
   352  func newOrderValid(order *corepb.Order) bool {
   353  	return !(order.RegistrationID == 0 || order.Expires == nil || len(order.Identifiers) == 0)
   354  }
   355  
   356  // PBToAuthzMap converts a protobuf map of identifiers mapped to protobuf
   357  // authorizations to a golang map[string]*core.Authorization.
   358  func PBToAuthzMap(pb *sapb.Authorizations) (map[identifier.ACMEIdentifier]*core.Authorization, error) {
   359  	m := make(map[identifier.ACMEIdentifier]*core.Authorization, len(pb.Authzs))
   360  	for _, v := range pb.Authzs {
   361  		authz, err := PBToAuthz(v)
   362  		if err != nil {
   363  			return nil, err
   364  		}
   365  		m[authz.Identifier] = &authz
   366  	}
   367  	return m, nil
   368  }