Skip to content

Commit

Permalink
Add CSR support for key delivery and proof of possession (#527)
Browse files Browse the repository at this point in the history
* Add CSR support for key delivery and proof of possession

Many ecosystems and libraries have support for generating CSRs, but do
not have easy-to-use utilities for generating encoded public keys. A CSR
provides a simple way to send an encoded public key, along with a proof
of possession since CSRs are self-signed.

Existing clients do not have to change their behavior, as we will
continue to support providing a public key and signed challenge.

Signed-off-by: Hayden Blauzvern <[email protected]>

* Fix linter

Signed-off-by: Hayden Blauzvern <[email protected]>

* Change gRPC proto to have oneof for pubkey and CSR

This provides a cleaner API.

Signed-off-by: Hayden Blauzvern <[email protected]>

* Fix proto in example

Signed-off-by: Hayden Blauzvern <[email protected]>

* Fix spelling

Signed-off-by: Hayden Blauzvern <[email protected]>

* Update comment to specify support public key types

Signed-off-by: Hayden Blauzvern <[email protected]>
  • Loading branch information
haydentherapper authored Apr 20, 2022
1 parent c165bc7 commit 59f9be8
Show file tree
Hide file tree
Showing 12 changed files with 1,057 additions and 375 deletions.
10 changes: 7 additions & 3 deletions examples/request-certificate/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,14 @@ func GetCert(signer *signature.RSAPKCS1v15SignerVerifier, fc fulciopb.CAClient,
OidcIdentityToken: tok.RawString,
},
},
PublicKey: &fulciopb.PublicKey{
Content: string(pubBytesPEM),
Key: &fulciopb.CreateSigningCertificateRequest_PublicKeyRequest{
PublicKeyRequest: &fulciopb.PublicKeyRequest{
PublicKey: &fulciopb.PublicKey{
Content: string(pubBytesPEM),
},
ProofOfPossession: proof,
},
},
ProofOfPossession: proof,
}
return fc.CreateSigningCertificate(context.Background(), cscr)
}
Expand Down
41 changes: 30 additions & 11 deletions fulcio.proto
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,21 @@ message CreateSigningCertificateRequest {
* Identity information about who possesses the private / public key pair presented
*/
Credentials credentials = 1 [(google.api.field_behavior) = REQUIRED];
/*
* The public key to be stored in the requested certificate
*/
PublicKey public_key = 2 [(google.api.field_behavior) = REQUIRED];
/*
* Proof that the client possesses the private key; must be verifiable by provided public key
*
* This is a currently a signature over the `sub` claim from the OIDC identity token
*/
bytes proof_of_possession = 3 [(google.api.field_behavior) = REQUIRED];
oneof key {
/*
* The public key to be stored in the requested certificate along with a signed
* challenge as proof of possession of the private key.
*/
PublicKeyRequest public_key_request = 2 [(google.api.field_behavior) = REQUIRED];
/*
* PKCS#10 PEM-encoded certificate signing request
*
* Contains the public key to be stored in the requested certificate. All other CSR fields
* are ignored. Since the CSR is self-signed, it also acts as a proof of posession of
* the private key.
*/
bytes certificate_signing_request = 3 [(google.api.field_behavior) = REQUIRED];
}
}

message Credentials {
Expand All @@ -70,13 +75,27 @@ message Credentials {
}
}

message PublicKeyRequest {
/*
* The public key to be stored in the requested certificate
*/
PublicKey public_key = 1 [(google.api.field_behavior) = REQUIRED];
/*
* Proof that the client possesses the private key; must be verifiable by provided public key
*
* This is a currently a signature over the `sub` claim from the OIDC identity token
*/
bytes proof_of_possession = 2 [(google.api.field_behavior) = REQUIRED];
}

message PublicKey {
/*
* The cryptographic algorithm to use with the key material
*/
PublicKeyAlgorithm algorithm = 1;
/*
* PEM encoded public key
* PKIX, ASN.1 DER or PEM-encoded public key. PEM is typically
* of type PUBLIC KEY.
*/
string content = 2 [(google.api.field_behavior) = REQUIRED];
}
Expand Down
18 changes: 15 additions & 3 deletions fulcio_legacy.proto
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,26 @@ message CreateSigningCertificateRequest {
*/
PublicKey publicKey = 1 [
deprecated=true,
(google.api.field_behavior) = REQUIRED
(google.api.field_behavior) = OPTIONAL
];
/*
* Proof that the client possesses the private key
*/
bytes signedEmailAddress = 2 [
deprecated=true,
(google.api.field_behavior) = REQUIRED
(google.api.field_behavior) = OPTIONAL
];
/*
* Optional: PKCS#10 PEM-encoded certificate signing request
* Contains the public key to be stored in the requested
* certificate. All other CSR fields are ignored. Since
* the CSR is self-signed, it also acts as a proof of
* posession of the private key.
*/
bytes certificateSigningRequest = 3 [
deprecated=true,
(google.api.field_behavior) = OPTIONAL
];
}

message PublicKey {
Expand All @@ -75,7 +86,8 @@ message PublicKey {
*/
string algorithm = 1 [ deprecated=true ];
/*
* DER or PEM encoded public key
* PKIX, ASN.1 DER or PEM-encoded public key. PEM is typically
* of type PUBLIC KEY.
*/
bytes content = 2 [
deprecated=true,
Expand Down
7 changes: 5 additions & 2 deletions pkg/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,14 @@ type Key struct {
}

type CertificateRequest struct {
// +required
// +optional
PublicKey Key `json:"publicKey"`

// +required
// +optional
SignedEmailAddress []byte `json:"signedEmailAddress"`

// +optional
CertificateSigningRequest []byte `json:"certificateSigningRequest"`
}

const (
Expand Down
1 change: 1 addition & 0 deletions pkg/api/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
const (
invalidSignature = "The signature supplied in the request could not be verified"
invalidPublicKey = "The public key supplied in the request could not be parsed"
invalidCSR = "The certificate signing request could not be parsed"
failedToEnterCertInCTL = "Error entering certificate in CTL"
failedToMarshalSCT = "Error marshaling signed certificate timestamp"
failedToMarshalCert = "Error marshaling code signing certificate"
Expand Down
35 changes: 21 additions & 14 deletions pkg/api/grpc_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
"crypto/x509"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"strings"

Expand Down Expand Up @@ -76,28 +75,36 @@ func (g *grpcCAServer) CreateSigningCertificate(ctx context.Context, request *fu
return nil, handleFulcioGRPCError(ctx, codes.Unauthenticated, err, invalidCredentials)
}

if request.PublicKey == nil {
return nil, handleFulcioGRPCError(ctx, codes.InvalidArgument, errors.New("public key not provided"), invalidPublicKey)
// optionally parse CSR
var csr *x509.CertificateRequest
if len(request.GetCertificateSigningRequest()) > 0 {
csr, err = challenges.ParseCSR(request.GetCertificateSigningRequest())
if err != nil {
return nil, handleFulcioGRPCError(ctx, codes.InvalidArgument, err, invalidCSR)
}
}

publicKeyBytes := request.PublicKey.Content
// try to unmarshal as PEM
publicKey, err := cryptoutils.UnmarshalPEMToPublicKey([]byte(publicKeyBytes))
if err != nil {
// try to unmarshal as DER
logger.Debugf("error parsing public key as PEM, trying DER: %v", err.Error())
publicKey, err = x509.ParsePKIXPublicKey([]byte(publicKeyBytes))
if err != nil {
return nil, handleFulcioGRPCError(ctx, codes.InvalidArgument, err, invalidPublicKey)
// fetch public key from request or CSR
var pubKeyContent string
var proofOfPossession []byte
if request.GetPublicKeyRequest() != nil {
if request.GetPublicKeyRequest().PublicKey != nil {
pubKeyContent = request.GetPublicKeyRequest().PublicKey.Content
}
proofOfPossession = request.GetPublicKeyRequest().ProofOfPossession
}
publicKey, err := challenges.ParsePublicKey(pubKeyContent, csr)
if err != nil {
return nil, handleFulcioGRPCError(ctx, codes.InvalidArgument, err, invalidPublicKey)
}

// Validate public key, checking for weak key parameters.
// validate public key, checking for weak key parameters
if err := cryptoutils.ValidatePubKey(publicKey); err != nil {
return nil, handleFulcioGRPCError(ctx, codes.InvalidArgument, err, insecurePublicKey)
}

subject, err := challenges.ExtractSubject(ctx, principal, publicKey, request.ProofOfPossession)
// verify challenge
subject, err := challenges.ExtractSubject(ctx, principal, publicKey, csr, proofOfPossession)
if err != nil {
return nil, handleFulcioGRPCError(ctx, codes.InvalidArgument, err, invalidSignature)
}
Expand Down
Loading

0 comments on commit 59f9be8

Please sign in to comment.