Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CSR support for key delivery and proof of possession #527

Merged
merged 6 commits into from
Apr 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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];
haydentherapper marked this conversation as resolved.
Show resolved Hide resolved
/*
* 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