2015-11-18 15:16:09 +00:00
package pki
import (
2017-09-01 03:07:15 +00:00
"crypto/rand"
"crypto/x509"
2015-11-18 15:16:09 +00:00
"encoding/base64"
2017-09-01 03:07:15 +00:00
"encoding/pem"
2015-11-18 15:16:09 +00:00
"fmt"
2017-09-01 03:07:15 +00:00
"reflect"
2017-08-31 19:46:13 +00:00
"time"
2015-11-18 15:16:09 +00:00
2017-09-01 03:07:15 +00:00
"github.com/hashicorp/errwrap"
2016-07-28 19:19:27 +00:00
"github.com/hashicorp/vault/helper/errutil"
2015-11-18 15:16:09 +00:00
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
)
func pathGenerateRoot ( b * backend ) * framework . Path {
ret := & framework . Path {
Pattern : "root/generate/" + framework . GenericNameRegex ( "exported" ) ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
2016-01-07 15:30:47 +00:00
logical . UpdateOperation : b . pathCAGenerateRoot ,
2015-11-18 15:16:09 +00:00
} ,
HelpSynopsis : pathGenerateRootHelpSyn ,
HelpDescription : pathGenerateRootHelpDesc ,
}
ret . Fields = addCACommonFields ( map [ string ] * framework . FieldSchema { } )
ret . Fields = addCAKeyGenerationFields ( ret . Fields )
ret . Fields = addCAIssueFields ( ret . Fields )
return ret
}
2017-08-15 18:00:40 +00:00
func pathDeleteRoot ( b * backend ) * framework . Path {
ret := & framework . Path {
Pattern : "root" ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
logical . DeleteOperation : b . pathCADeleteRoot ,
} ,
HelpSynopsis : pathDeleteRootHelpSyn ,
HelpDescription : pathDeleteRootHelpDesc ,
}
return ret
}
2015-11-18 15:16:09 +00:00
func pathSignIntermediate ( b * backend ) * framework . Path {
ret := & framework . Path {
Pattern : "root/sign-intermediate" ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
2016-01-07 15:30:47 +00:00
logical . UpdateOperation : b . pathCASignIntermediate ,
2015-11-18 15:16:09 +00:00
} ,
HelpSynopsis : pathSignIntermediateHelpSyn ,
HelpDescription : pathSignIntermediateHelpDesc ,
}
ret . Fields = addCACommonFields ( map [ string ] * framework . FieldSchema { } )
ret . Fields = addCAIssueFields ( ret . Fields )
ret . Fields [ "csr" ] = & framework . FieldSchema {
Type : framework . TypeString ,
Default : "" ,
Description : ` PEM-format CSR to be signed. ` ,
}
ret . Fields [ "use_csr_values" ] = & framework . FieldSchema {
Type : framework . TypeBool ,
Default : false ,
Description : ` If true , then :
1 ) Subject information , including names and alternate
names , will be preserved from the CSR rather than
using values provided in the other parameters to
this path ;
2 ) Any key usages requested in the CSR will be
added to the basic set of key usages used for CA
certs signed by this path ; for instance ,
the non - repudiation flag . ` ,
}
return ret
}
2017-08-31 19:46:13 +00:00
func pathSignSelfIssued ( b * backend ) * framework . Path {
ret := & framework . Path {
Pattern : "root/sign-self-issued" ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
2017-09-01 03:07:15 +00:00
logical . UpdateOperation : b . pathCASignSelfIssued ,
} ,
Fields : map [ string ] * framework . FieldSchema {
"certificate" : & framework . FieldSchema {
Type : framework . TypeString ,
Description : ` PEM-format self-issued certificate to be signed. ` ,
} ,
2017-08-31 19:46:13 +00:00
} ,
HelpSynopsis : pathSignSelfIssuedHelpSyn ,
HelpDescription : pathSignSelfIssuedHelpDesc ,
}
return ret
}
2017-08-15 18:00:40 +00:00
func ( b * backend ) pathCADeleteRoot (
req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
return nil , req . Storage . Delete ( "config/ca_bundle" )
}
2015-11-18 15:16:09 +00:00
func ( b * backend ) pathCAGenerateRoot (
req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
var err error
2017-08-15 18:00:40 +00:00
entry , err := req . Storage . Get ( "config/ca_bundle" )
if err != nil {
return nil , err
}
if entry != nil {
return nil , nil
}
2015-11-18 15:16:09 +00:00
exported , format , role , errorResp := b . getGenerationParams ( data )
if errorResp != nil {
return errorResp , nil
}
maxPathLengthIface , ok := data . GetOk ( "max_path_length" )
if ok {
maxPathLength := maxPathLengthIface . ( int )
role . MaxPathLength = & maxPathLength
}
parsedBundle , err := generateCert ( b , role , nil , true , req , data )
if err != nil {
switch err . ( type ) {
2016-07-28 19:19:27 +00:00
case errutil . UserError :
2015-11-18 15:16:09 +00:00
return logical . ErrorResponse ( err . Error ( ) ) , nil
2016-07-28 19:19:27 +00:00
case errutil . InternalError :
2015-11-18 15:16:09 +00:00
return nil , err
}
}
cb , err := parsedBundle . ToCertBundle ( )
if err != nil {
2017-10-27 16:02:18 +00:00
return nil , errwrap . Wrapf ( "error converting raw cert bundle to cert bundle: {{err}}" , err )
2015-11-18 15:16:09 +00:00
}
2016-05-09 23:53:28 +00:00
resp := & logical . Response {
Data : map [ string ] interface { } {
2015-11-18 15:16:09 +00:00
"expiration" : int64 ( parsedBundle . Certificate . NotAfter . Unix ( ) ) ,
2015-11-19 21:51:27 +00:00
"serial_number" : cb . SerialNumber ,
2015-11-18 15:16:09 +00:00
} ,
2016-05-09 23:53:28 +00:00
}
2015-11-18 15:16:09 +00:00
switch format {
case "pem" :
2016-02-01 18:19:41 +00:00
resp . Data [ "certificate" ] = cb . Certificate
2016-09-28 00:50:17 +00:00
resp . Data [ "issuing_ca" ] = cb . Certificate
2015-11-18 15:16:09 +00:00
if exported {
resp . Data [ "private_key" ] = cb . PrivateKey
resp . Data [ "private_key_type" ] = cb . PrivateKeyType
}
2016-02-01 18:19:41 +00:00
case "pem_bundle" :
2016-09-28 00:50:17 +00:00
resp . Data [ "issuing_ca" ] = cb . Certificate
2016-02-01 18:19:41 +00:00
if exported {
resp . Data [ "private_key" ] = cb . PrivateKey
resp . Data [ "private_key_type" ] = cb . PrivateKeyType
2016-09-28 00:50:17 +00:00
resp . Data [ "certificate" ] = fmt . Sprintf ( "%s\n%s" , cb . PrivateKey , cb . Certificate )
2016-02-01 18:19:41 +00:00
} else {
2016-09-28 00:50:17 +00:00
resp . Data [ "certificate" ] = cb . Certificate
2016-02-01 18:19:41 +00:00
}
2015-11-18 15:16:09 +00:00
case "der" :
resp . Data [ "certificate" ] = base64 . StdEncoding . EncodeToString ( parsedBundle . CertificateBytes )
2016-09-28 00:50:17 +00:00
resp . Data [ "issuing_ca" ] = base64 . StdEncoding . EncodeToString ( parsedBundle . CertificateBytes )
2015-11-18 15:16:09 +00:00
if exported {
resp . Data [ "private_key" ] = base64 . StdEncoding . EncodeToString ( parsedBundle . PrivateKeyBytes )
resp . Data [ "private_key_type" ] = cb . PrivateKeyType
}
}
2015-11-19 21:51:27 +00:00
// Store it as the CA bundle
2017-08-15 18:00:40 +00:00
entry , err = logical . StorageEntryJSON ( "config/ca_bundle" , cb )
2015-11-18 15:16:09 +00:00
if err != nil {
return nil , err
}
err = req . Storage . Put ( entry )
if err != nil {
return nil , err
}
2015-11-19 21:51:27 +00:00
// Also store it as just the certificate identified by serial number, so it
// can be revoked
err = req . Storage . Put ( & logical . StorageEntry {
2017-05-03 14:12:58 +00:00
Key : "certs/" + normalizeSerial ( cb . SerialNumber ) ,
2015-11-19 21:51:27 +00:00
Value : parsedBundle . CertificateBytes ,
} )
if err != nil {
2017-10-27 16:02:18 +00:00
return nil , errwrap . Wrapf ( "unable to store certificate locally: {{err}}" , err )
2015-11-19 21:51:27 +00:00
}
2015-11-18 15:16:09 +00:00
// For ease of later use, also store just the certificate at a known
2015-11-19 21:51:27 +00:00
// location
2015-11-18 15:16:09 +00:00
entry . Key = "ca"
entry . Value = parsedBundle . CertificateBytes
err = req . Storage . Put ( entry )
if err != nil {
return nil , err
}
2015-11-19 21:51:27 +00:00
// Build a fresh CRL
2015-11-18 15:16:09 +00:00
err = buildCRL ( b , req )
if err != nil {
return nil , err
}
if parsedBundle . Certificate . MaxPathLen == 0 {
resp . AddWarning ( "Max path length of the generated certificate is zero. This certificate cannot be used to issue intermediate CA certificates." )
}
return resp , nil
}
func ( b * backend ) pathCASignIntermediate (
req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
var err error
format := getFormat ( data )
if format == "" {
return logical . ErrorResponse (
` The "format" path parameter must be "pem" or "der" ` ,
) , nil
}
role := & roleEntry {
2017-09-13 15:42:45 +00:00
TTL : ( time . Duration ( data . Get ( "ttl" ) . ( int ) ) * time . Second ) . String ( ) ,
AllowLocalhost : true ,
AllowAnyName : true ,
AllowIPSANs : true ,
EnforceHostnames : false ,
KeyType : "any" ,
AllowExpirationPastCA : true ,
2015-11-18 15:16:09 +00:00
}
if cn := data . Get ( "common_name" ) . ( string ) ; len ( cn ) == 0 {
role . UseCSRCommonName = true
}
var caErr error
signingBundle , caErr := fetchCAInfo ( req )
switch caErr . ( type ) {
2016-07-28 19:19:27 +00:00
case errutil . UserError :
return nil , errutil . UserError { Err : fmt . Sprintf (
2015-11-18 15:16:09 +00:00
"could not fetch the CA certificate (was one set?): %s" , caErr ) }
2016-07-28 19:19:27 +00:00
case errutil . InternalError :
return nil , errutil . InternalError { Err : fmt . Sprintf (
2015-11-18 15:16:09 +00:00
"error fetching CA certificate: %s" , caErr ) }
}
useCSRValues := data . Get ( "use_csr_values" ) . ( bool )
maxPathLengthIface , ok := data . GetOk ( "max_path_length" )
if ok {
maxPathLength := maxPathLengthIface . ( int )
role . MaxPathLength = & maxPathLength
}
parsedBundle , err := signCert ( b , role , signingBundle , true , useCSRValues , req , data )
if err != nil {
switch err . ( type ) {
2016-07-28 19:19:27 +00:00
case errutil . UserError :
2015-11-18 15:16:09 +00:00
return logical . ErrorResponse ( err . Error ( ) ) , nil
2016-07-28 19:19:27 +00:00
case errutil . InternalError :
2015-11-18 15:16:09 +00:00
return nil , err
}
}
2016-09-28 00:50:17 +00:00
if err := parsedBundle . Verify ( ) ; err != nil {
return nil , fmt . Errorf ( "verification of parsed bundle failed: %s" , err )
}
signingCB , err := signingBundle . ToCertBundle ( )
if err != nil {
return nil , fmt . Errorf ( "Error converting raw signing bundle to cert bundle: %s" , err )
}
2015-11-18 15:16:09 +00:00
cb , err := parsedBundle . ToCertBundle ( )
if err != nil {
return nil , fmt . Errorf ( "Error converting raw cert bundle to cert bundle: %s" , err )
}
2016-05-09 23:53:28 +00:00
resp := & logical . Response {
Data : map [ string ] interface { } {
2015-11-18 15:16:09 +00:00
"expiration" : int64 ( parsedBundle . Certificate . NotAfter . Unix ( ) ) ,
"serial_number" : cb . SerialNumber ,
} ,
2016-05-09 23:53:28 +00:00
}
2015-11-18 15:16:09 +00:00
2017-09-13 15:42:45 +00:00
if signingBundle . Certificate . NotAfter . Before ( parsedBundle . Certificate . NotAfter ) {
resp . AddWarning ( "The expiration time for the signed certificate is after the CA's expiration time. If the new certificate is not treated as a root, validation paths with the certificate past the issuing CA's expiration time will fail." )
}
2016-02-01 18:19:41 +00:00
switch format {
case "pem" :
resp . Data [ "certificate" ] = cb . Certificate
2016-09-28 00:50:17 +00:00
resp . Data [ "issuing_ca" ] = signingCB . Certificate
if cb . CAChain != nil && len ( cb . CAChain ) > 0 {
resp . Data [ "ca_chain" ] = cb . CAChain
}
2016-02-01 18:19:41 +00:00
case "pem_bundle" :
2016-09-28 00:50:17 +00:00
resp . Data [ "certificate" ] = cb . ToPEMBundle ( )
resp . Data [ "issuing_ca" ] = signingCB . Certificate
if cb . CAChain != nil && len ( cb . CAChain ) > 0 {
resp . Data [ "ca_chain" ] = cb . CAChain
}
2016-02-01 18:19:41 +00:00
case "der" :
2015-11-18 15:16:09 +00:00
resp . Data [ "certificate" ] = base64 . StdEncoding . EncodeToString ( parsedBundle . CertificateBytes )
2016-09-28 00:50:17 +00:00
resp . Data [ "issuing_ca" ] = base64 . StdEncoding . EncodeToString ( signingBundle . CertificateBytes )
var caChain [ ] string
for _ , caCert := range parsedBundle . CAChain {
caChain = append ( caChain , base64 . StdEncoding . EncodeToString ( caCert . Bytes ) )
}
if caChain != nil && len ( caChain ) > 0 {
resp . Data [ "ca_chain" ] = cb . CAChain
}
2015-11-18 15:16:09 +00:00
}
err = req . Storage . Put ( & logical . StorageEntry {
2017-05-03 14:12:58 +00:00
Key : "certs/" + normalizeSerial ( cb . SerialNumber ) ,
2015-11-18 15:16:09 +00:00
Value : parsedBundle . CertificateBytes ,
} )
if err != nil {
2016-12-19 15:49:32 +00:00
return nil , fmt . Errorf ( "Unable to store certificate locally: %v" , err )
2015-11-18 15:16:09 +00:00
}
if parsedBundle . Certificate . MaxPathLen == 0 {
resp . AddWarning ( "Max path length of the signed certificate is zero. This certificate cannot be used to issue intermediate CA certificates." )
}
return resp , nil
}
2017-09-01 03:07:15 +00:00
func ( b * backend ) pathCASignSelfIssued (
req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
var err error
certPem := data . Get ( "certificate" ) . ( string )
block , _ := pem . Decode ( [ ] byte ( certPem ) )
if block == nil || len ( block . Bytes ) == 0 {
return logical . ErrorResponse ( "certificate could not be PEM-decoded" ) , nil
}
certs , err := x509 . ParseCertificates ( block . Bytes )
if err != nil {
return logical . ErrorResponse ( fmt . Sprintf ( "error parsing certificate: %s" , err ) ) , nil
}
if len ( certs ) != 1 {
return logical . ErrorResponse ( fmt . Sprintf ( "%d certificates found in PEM file, expected 1" , len ( certs ) ) ) , nil
}
cert := certs [ 0 ]
if ! cert . IsCA {
return logical . ErrorResponse ( "given certificate is not a CA certificate" ) , nil
}
if ! reflect . DeepEqual ( cert . Issuer , cert . Subject ) {
return logical . ErrorResponse ( "given certificate is not self-issued" ) , nil
}
var caErr error
signingBundle , caErr := fetchCAInfo ( req )
switch caErr . ( type ) {
case errutil . UserError :
return nil , errutil . UserError { Err : fmt . Sprintf (
"could not fetch the CA certificate (was one set?): %s" , caErr ) }
case errutil . InternalError :
return nil , errutil . InternalError { Err : fmt . Sprintf (
"error fetching CA certificate: %s" , caErr ) }
}
signingCB , err := signingBundle . ToCertBundle ( )
if err != nil {
return nil , fmt . Errorf ( "Error converting raw signing bundle to cert bundle: %s" , err )
}
urls := & urlEntries { }
if signingBundle . URLs != nil {
urls = signingBundle . URLs
}
cert . IssuingCertificateURL = urls . IssuingCertificates
cert . CRLDistributionPoints = urls . CRLDistributionPoints
cert . OCSPServer = urls . OCSPServers
2017-09-13 15:42:45 +00:00
newCert , err := x509 . CreateCertificate ( rand . Reader , cert , signingBundle . Certificate , cert . PublicKey , signingBundle . PrivateKey )
2017-09-01 03:07:15 +00:00
if err != nil {
return nil , errwrap . Wrapf ( "error signing self-issued certificate: {{err}}" , err )
}
if len ( newCert ) == 0 {
return nil , fmt . Errorf ( "nil cert was created when signing self-issued certificate" )
}
pemCert := pem . EncodeToMemory ( & pem . Block {
Type : "CERTIFICATE" ,
Bytes : newCert ,
} )
return & logical . Response {
Data : map [ string ] interface { } {
"certificate" : string ( pemCert ) ,
"issuing_ca" : signingCB . Certificate ,
} ,
} , nil
}
2015-11-18 15:16:09 +00:00
const pathGenerateRootHelpSyn = `
Generate a new CA certificate and private key used for signing .
`
const pathGenerateRootHelpDesc = `
See the API documentation for more information .
`
2017-08-15 18:00:40 +00:00
const pathDeleteRootHelpSyn = `
Deletes the root CA key to allow a new one to be generated .
`
const pathDeleteRootHelpDesc = `
See the API documentation for more information .
`
2015-11-18 15:16:09 +00:00
const pathSignIntermediateHelpSyn = `
Issue an intermediate CA certificate based on the provided CSR .
`
const pathSignIntermediateHelpDesc = `
2017-08-31 19:46:13 +00:00
see the API documentation for more information .
`
const pathSignSelfIssuedHelpSyn = `
Signs another CA ' s self - issued certificate .
`
const pathSignSelfIssuedHelpDesc = `
Signs another CA ' s self - issued certificate . This is most often used for rolling roots ; unless you know you need this you probably want to use sign - intermediate instead .
2017-09-13 15:42:45 +00:00
Note that this is a very privileged operation and should be extremely restricted in terms of who is allowed to use it . All values will be taken directly from the incoming certificate and only verification that it is self - issued will be performed .
Configured URLs for CRLs / OCSP / etc . will be copied over and the issuer will be this mount ' s CA cert . Other than that , all other values will be used verbatim .
2015-11-18 15:16:09 +00:00
`