2018-10-05 21:48:36 +00:00
package authority
import (
2019-07-29 19:34:27 +00:00
"context"
2020-09-18 23:25:08 +00:00
"crypto/sha256"
2018-11-01 22:43:24 +00:00
"crypto/x509"
2020-09-18 23:25:08 +00:00
"encoding/hex"
2018-10-05 21:48:36 +00:00
"net/http"
2021-07-21 22:22:57 +00:00
"strconv"
2019-03-18 17:59:36 +00:00
"strings"
2021-05-03 19:48:20 +00:00
"time"
2018-10-05 21:48:36 +00:00
2021-05-03 19:48:20 +00:00
"github.com/smallstep/certificates/authority/admin"
2019-03-06 23:04:28 +00:00
"github.com/smallstep/certificates/authority/provisioner"
2019-12-20 21:30:05 +00:00
"github.com/smallstep/certificates/errs"
2020-08-24 21:44:11 +00:00
"go.step.sm/crypto/jose"
2021-05-03 19:48:20 +00:00
"go.step.sm/linkedca"
2019-12-20 21:30:05 +00:00
"golang.org/x/crypto/ssh"
2018-10-05 21:48:36 +00:00
)
2019-03-06 23:04:28 +00:00
// Claims extends jose.Claims with step attributes.
2019-01-30 23:36:42 +00:00
type Claims struct {
2019-03-06 23:04:28 +00:00
jose . Claims
2019-03-07 01:00:45 +00:00
SANs [ ] string ` json:"sans,omitempty" `
Email string ` json:"email,omitempty" `
Nonce string ` json:"nonce,omitempty" `
2019-01-30 23:36:42 +00:00
}
2019-11-20 19:51:25 +00:00
type skipTokenReuseKey struct { }
// NewContextWithSkipTokenReuse creates a new context from ctx and attaches a
// value to skip the token reuse.
func NewContextWithSkipTokenReuse ( ctx context . Context ) context . Context {
return context . WithValue ( ctx , skipTokenReuseKey { } , true )
}
// SkipTokenReuseFromContext returns if the token reuse needs to be ignored.
func SkipTokenReuseFromContext ( ctx context . Context ) bool {
m , _ := ctx . Value ( skipTokenReuseKey { } ) . ( bool )
return m
}
2019-03-05 08:07:13 +00:00
// authorizeToken parses the token and returns the provisioner used to generate
// the token. This method enforces the One-Time use policy (tokens can only be
// used once).
2019-12-20 21:30:05 +00:00
func ( a * Authority ) authorizeToken ( ctx context . Context , token string ) ( provisioner . Interface , error ) {
2018-10-05 21:48:36 +00:00
// Validate payload
2019-12-20 21:30:05 +00:00
tok , err := jose . ParseSigned ( token )
2018-10-05 21:48:36 +00:00
if err != nil {
2019-12-20 21:30:05 +00:00
return nil , errs . Wrap ( http . StatusUnauthorized , err , "authority.authorizeToken: error parsing token" )
2018-10-05 21:48:36 +00:00
}
2018-10-30 01:00:30 +00:00
// Get claims w/out verification. We need to look up the provisioner
// key in order to verify the claims and we need the issuer from the claims
// before we can look up the provisioner.
2019-03-07 01:00:45 +00:00
var claims Claims
2019-12-20 21:30:05 +00:00
if err = tok . UnsafeClaimsWithoutVerification ( & claims ) ; err != nil {
return nil , errs . Wrap ( http . StatusUnauthorized , err , "authority.authorizeToken" )
2018-10-30 01:00:30 +00:00
}
2018-10-05 21:48:36 +00:00
2019-03-05 08:07:13 +00:00
// TODO: use new persistence layer abstraction.
2018-10-25 01:59:48 +00:00
// Do not accept tokens issued before the start of the ca.
// This check is meant as a stopgap solution to the current lack of a persistence layer.
if a . config . AuthorityConfig != nil && ! a . config . AuthorityConfig . DisableIssuedAtCheck {
2019-04-05 19:54:23 +00:00
if claims . IssuedAt != nil && claims . IssuedAt . Time ( ) . Before ( a . startTime ) {
2020-01-24 06:04:34 +00:00
return nil , errs . Unauthorized ( "authority.authorizeToken: token issued before the bootstrap of certificate authority" )
2018-10-25 01:59:48 +00:00
}
}
2019-03-07 01:37:49 +00:00
// This method will also validate the audiences for JWK provisioners.
2019-12-20 21:30:05 +00:00
p , ok := a . provisioners . LoadByToken ( tok , & claims . Claims )
2019-03-06 23:04:28 +00:00
if ! ok {
2020-01-24 06:04:34 +00:00
return nil , errs . Unauthorized ( "authority.authorizeToken: provisioner " +
"not found or invalid audience (%s)" , strings . Join ( claims . Audience , ", " ) )
2018-10-05 21:48:36 +00:00
}
2018-10-19 05:26:39 +00:00
2019-11-20 19:51:25 +00:00
// Store the token to protect against reuse unless it's skipped.
2020-09-18 23:25:08 +00:00
// If we cannot get a token id from the provisioner, just hash the token.
2019-11-20 19:51:25 +00:00
if ! SkipTokenReuseFromContext ( ctx ) {
2021-05-03 19:48:20 +00:00
if err = a . UseToken ( token , p ) ; err != nil {
return nil , err
2019-03-07 01:00:45 +00:00
}
}
2019-03-06 23:04:28 +00:00
2019-03-05 08:07:13 +00:00
return p , nil
}
2021-05-03 19:48:20 +00:00
// AuthorizeAdminToken authorize an Admin token.
func ( a * Authority ) AuthorizeAdminToken ( r * http . Request , token string ) ( * linkedca . Admin , error ) {
jwt , err := jose . ParseSigned ( token )
if err != nil {
return nil , admin . WrapError ( admin . ErrorUnauthorizedType , err , "adminHandler.authorizeToken; error parsing x5c token" )
}
verifiedChains , err := jwt . Headers [ 0 ] . Certificates ( x509 . VerifyOptions {
Roots : a . rootX509CertPool ,
KeyUsages : [ ] x509 . ExtKeyUsage { x509 . ExtKeyUsageClientAuth } ,
} )
if err != nil {
return nil , admin . WrapError ( admin . ErrorUnauthorizedType , err ,
"adminHandler.authorizeToken; error verifying x5c certificate chain in token" )
}
leaf := verifiedChains [ 0 ] [ 0 ]
if leaf . KeyUsage & x509 . KeyUsageDigitalSignature == 0 {
return nil , admin . NewError ( admin . ErrorUnauthorizedType , "adminHandler.authorizeToken; certificate used to sign x5c token cannot be used for digital signature" )
}
// Using the leaf certificates key to validate the claims accomplishes two
// things:
// 1. Asserts that the private key used to sign the token corresponds
// to the public certificate in the `x5c` header of the token.
// 2. Asserts that the claims are valid - have not been tampered with.
var claims jose . Claims
if err = jwt . Claims ( leaf . PublicKey , & claims ) ; err != nil {
return nil , admin . WrapError ( admin . ErrorUnauthorizedType , err , "adminHandler.authorizeToken; error parsing x5c claims" )
}
prov , err := a . LoadProvisionerByCertificate ( leaf )
if err != nil {
return nil , err
}
// Check that the token has not been used.
if err = a . UseToken ( token , prov ) ; err != nil {
return nil , admin . WrapError ( admin . ErrorUnauthorizedType , err , "adminHandler.authorizeToken; error with reuse token" )
}
// According to "rfc7519 JSON Web Token" acceptable skew should be no
// more than a few minutes.
if err = claims . ValidateWithLeeway ( jose . Expected {
Issuer : prov . GetName ( ) ,
Time : time . Now ( ) . UTC ( ) ,
} , time . Minute ) ; err != nil {
return nil , admin . WrapError ( admin . ErrorUnauthorizedType , err , "x5c.authorizeToken; invalid x5c claims" )
}
// validate audience: path matches the current path
if r . URL . Path != claims . Audience [ 0 ] {
return nil , admin . NewError ( admin . ErrorUnauthorizedType ,
"x5c.authorizeToken; x5c token has invalid audience " +
"claim (aud); expected %s, but got %s" , r . URL . Path , claims . Audience )
}
if claims . Subject == "" {
return nil , admin . NewError ( admin . ErrorUnauthorizedType ,
"x5c.authorizeToken; x5c token subject cannot be empty" )
}
var (
ok bool
adm * linkedca . Admin
)
adminFound := false
adminSANs := append ( [ ] string { leaf . Subject . CommonName } , leaf . DNSNames ... )
adminSANs = append ( adminSANs , leaf . EmailAddresses ... )
for _ , san := range adminSANs {
if adm , ok = a . LoadAdminBySubProv ( san , claims . Issuer ) ; ok {
adminFound = true
break
}
}
if ! adminFound {
return nil , admin . NewError ( admin . ErrorUnauthorizedType ,
"adminHandler.authorizeToken; unable to load admin with subject(s) %s and provisioner '%s'" ,
adminSANs , claims . Issuer )
}
if strings . HasPrefix ( r . URL . Path , "/admin/admins" ) && ( r . Method != "GET" ) && adm . Type != linkedca . Admin_SUPER_ADMIN {
return nil , admin . NewError ( admin . ErrorUnauthorizedType , "must have super admin access to make this request" )
}
return adm , nil
}
// UseToken stores the token to protect against reuse.
2021-08-11 18:50:54 +00:00
//
// This method currently ignores any error coming from the GetTokenID, but it
// should specifically ignore the error provisioner.ErrAllowTokenReuse.
2021-05-03 19:48:20 +00:00
func ( a * Authority ) UseToken ( token string , prov provisioner . Interface ) error {
if reuseKey , err := prov . GetTokenID ( token ) ; err == nil {
if reuseKey == "" {
sum := sha256 . Sum256 ( [ ] byte ( token ) )
reuseKey = strings . ToLower ( hex . EncodeToString ( sum [ : ] ) )
}
ok , err := a . db . UseToken ( reuseKey , token )
if err != nil {
return errs . Wrap ( http . StatusInternalServerError , err ,
"authority.authorizeToken: failed when attempting to store token" )
}
if ! ok {
return errs . Unauthorized ( "authority.authorizeToken: token already used" )
}
}
return nil
}
2019-12-20 21:30:05 +00:00
// Authorize grabs the method from the context and authorizes the request by
// validating the one-time-token.
func ( a * Authority ) Authorize ( ctx context . Context , token string ) ( [ ] provisioner . SignOption , error ) {
2020-01-24 06:04:34 +00:00
var opts = [ ] interface { } { errs . WithKeyVal ( "token" , token ) }
2019-12-20 21:30:05 +00:00
2019-07-29 19:34:27 +00:00
switch m := provisioner . MethodFromContext ( ctx ) ; m {
2019-08-01 22:04:56 +00:00
case provisioner . SignMethod :
2019-12-20 21:30:05 +00:00
signOpts , err := a . authorizeSign ( ctx , token )
return signOpts , errs . Wrap ( http . StatusInternalServerError , err , "authority.Authorize" , opts ... )
2019-10-28 18:50:43 +00:00
case provisioner . RevokeMethod :
2019-12-20 21:30:05 +00:00
return nil , errs . Wrap ( http . StatusInternalServerError , a . authorizeRevoke ( ctx , token ) , "authority.Authorize" , opts ... )
case provisioner . SSHSignMethod :
2019-08-01 22:04:56 +00:00
if a . sshCAHostCertSignKey == nil && a . sshCAUserCertSignKey == nil {
2020-01-24 06:04:34 +00:00
return nil , errs . NotImplemented ( "authority.Authorize; ssh certificate flows are not enabled" , opts ... )
2019-07-29 19:34:27 +00:00
}
2020-01-29 19:58:47 +00:00
signOpts , err := a . authorizeSSHSign ( ctx , token )
return signOpts , errs . Wrap ( http . StatusInternalServerError , err , "authority.Authorize" , opts ... )
2019-12-20 21:30:05 +00:00
case provisioner . SSHRenewMethod :
2019-10-28 18:50:43 +00:00
if a . sshCAHostCertSignKey == nil && a . sshCAUserCertSignKey == nil {
2020-01-24 06:04:34 +00:00
return nil , errs . NotImplemented ( "authority.Authorize; ssh certificate flows are not enabled" , opts ... )
2019-10-28 18:50:43 +00:00
}
2019-12-20 21:30:05 +00:00
_ , err := a . authorizeSSHRenew ( ctx , token )
return nil , errs . Wrap ( http . StatusInternalServerError , err , "authority.Authorize" , opts ... )
case provisioner . SSHRevokeMethod :
return nil , errs . Wrap ( http . StatusInternalServerError , a . authorizeSSHRevoke ( ctx , token ) , "authority.Authorize" , opts ... )
case provisioner . SSHRekeyMethod :
2019-10-28 18:50:43 +00:00
if a . sshCAHostCertSignKey == nil && a . sshCAUserCertSignKey == nil {
2020-01-24 06:04:34 +00:00
return nil , errs . NotImplemented ( "authority.Authorize; ssh certificate flows are not enabled" , opts ... )
2019-10-28 18:50:43 +00:00
}
2019-12-20 21:30:05 +00:00
_ , signOpts , err := a . authorizeSSHRekey ( ctx , token )
return signOpts , errs . Wrap ( http . StatusInternalServerError , err , "authority.Authorize" , opts ... )
2019-07-29 19:34:27 +00:00
default :
2020-01-24 06:04:34 +00:00
return nil , errs . InternalServer ( "authority.Authorize; method %d is not supported" , append ( [ ] interface { } { m } , opts ... ) ... )
2019-03-09 02:05:11 +00:00
}
2019-07-29 19:34:27 +00:00
}
2019-03-09 02:05:11 +00:00
2019-12-20 21:30:05 +00:00
// authorizeSign loads the provisioner from the token and calls the provisioner
// AuthorizeSign method. Returns a list of methods to apply to the signing flow.
func ( a * Authority ) authorizeSign ( ctx context . Context , token string ) ( [ ] provisioner . SignOption , error ) {
p , err := a . authorizeToken ( ctx , token )
2019-08-01 22:04:56 +00:00
if err != nil {
2019-12-20 21:30:05 +00:00
return nil , errs . Wrap ( http . StatusInternalServerError , err , "authority.authorizeSign" )
2019-08-01 22:04:56 +00:00
}
2019-12-20 21:30:05 +00:00
signOpts , err := p . AuthorizeSign ( ctx , token )
2019-08-01 22:04:56 +00:00
if err != nil {
2019-12-20 21:30:05 +00:00
return nil , errs . Wrap ( http . StatusInternalServerError , err , "authority.authorizeSign" )
2019-08-01 22:04:56 +00:00
}
2019-12-20 21:30:05 +00:00
return signOpts , nil
2019-08-01 22:04:56 +00:00
}
2019-07-29 19:34:27 +00:00
// AuthorizeSign authorizes a signature request by validating and authenticating
2019-12-20 21:30:05 +00:00
// a token that must be sent w/ the request.
2019-09-09 04:05:36 +00:00
//
// NOTE: This method is deprecated and should not be used. We make it available
// in the short term os as not to break existing clients.
2019-12-20 21:30:05 +00:00
func ( a * Authority ) AuthorizeSign ( token string ) ( [ ] provisioner . SignOption , error ) {
2019-07-29 19:34:27 +00:00
ctx := provisioner . NewContextWithMethod ( context . Background ( ) , provisioner . SignMethod )
2019-12-20 21:30:05 +00:00
return a . Authorize ( ctx , token )
2018-10-05 21:48:36 +00:00
}
2018-11-01 22:43:24 +00:00
2019-12-20 21:30:05 +00:00
// authorizeRevoke locates the provisioner used to generate the authenticating
// token and then performs the token validation flow.
2019-10-28 18:50:43 +00:00
func ( a * Authority ) authorizeRevoke ( ctx context . Context , token string ) error {
2019-11-20 19:51:25 +00:00
p , err := a . authorizeToken ( ctx , token )
2019-10-28 18:50:43 +00:00
if err != nil {
2019-12-20 21:30:05 +00:00
return errs . Wrap ( http . StatusInternalServerError , err , "authority.authorizeRevoke" )
2019-03-05 08:07:13 +00:00
}
2019-12-03 00:11:27 +00:00
if err = p . AuthorizeRevoke ( ctx , token ) ; err != nil {
2019-12-20 21:30:05 +00:00
return errs . Wrap ( http . StatusInternalServerError , err , "authority.authorizeRevoke" )
2019-10-28 18:50:43 +00:00
}
return nil
2019-03-05 08:07:13 +00:00
}
2019-12-20 21:30:05 +00:00
// authorizeRenew locates the provisioner (using the provisioner extension in the cert), and checks
2018-11-01 22:43:24 +00:00
// if for the configured provisioner, the renewal is enabled or not. If the
// extra extension cannot be found, authorize the renewal by default.
//
// TODO(mariano): should we authorize by default?
2019-12-20 21:30:05 +00:00
func ( a * Authority ) authorizeRenew ( cert * x509 . Certificate ) error {
2021-07-23 23:10:13 +00:00
var err error
var isRevoked bool
2020-01-24 06:04:34 +00:00
var opts = [ ] interface { } { errs . WithKeyVal ( "serialNumber" , cert . SerialNumber . String ( ) ) }
2019-03-05 08:07:13 +00:00
// Check the passive revocation table.
2021-07-23 23:10:13 +00:00
serial := cert . SerialNumber . String ( )
if lca , ok := a . adminDB . ( interface {
IsRevoked ( string ) ( bool , error )
} ) ; ok {
isRevoked , err = lca . IsRevoked ( serial )
} else {
isRevoked , err = a . db . IsRevoked ( serial )
}
2019-03-05 08:07:13 +00:00
if err != nil {
2019-12-20 21:30:05 +00:00
return errs . Wrap ( http . StatusInternalServerError , err , "authority.authorizeRenew" , opts ... )
2019-03-05 08:07:13 +00:00
}
if isRevoked {
2020-01-24 06:04:34 +00:00
return errs . Unauthorized ( "authority.authorizeRenew: certificate has been revoked" , opts ... )
2019-03-05 08:07:13 +00:00
}
2019-12-20 21:30:05 +00:00
p , ok := a . provisioners . LoadByCertificate ( cert )
2019-03-06 23:04:28 +00:00
if ! ok {
2020-01-24 06:04:34 +00:00
return errs . Unauthorized ( "authority.authorizeRenew: provisioner not found" , opts ... )
2019-03-06 23:04:28 +00:00
}
2019-12-20 21:30:05 +00:00
if err := p . AuthorizeRenew ( context . Background ( ) , cert ) ; err != nil {
return errs . Wrap ( http . StatusInternalServerError , err , "authority.authorizeRenew" , opts ... )
}
return nil
}
2021-07-21 22:22:57 +00:00
// authorizeSSHCertificate returns an error if the given certificate is revoked.
func ( a * Authority ) authorizeSSHCertificate ( ctx context . Context , cert * ssh . Certificate ) error {
2021-07-23 23:10:13 +00:00
var err error
var isRevoked bool
2021-07-21 22:22:57 +00:00
serial := strconv . FormatUint ( cert . Serial , 10 )
2021-07-23 23:10:13 +00:00
if lca , ok := a . adminDB . ( interface {
IsSSHRevoked ( string ) ( bool , error )
} ) ; ok {
isRevoked , err = lca . IsSSHRevoked ( serial )
} else {
isRevoked , err = a . db . IsSSHRevoked ( serial )
}
2021-07-21 22:22:57 +00:00
if err != nil {
return errs . Wrap ( http . StatusInternalServerError , err , "authority.authorizeSSHCertificate" , errs . WithKeyVal ( "serialNumber" , serial ) )
}
if isRevoked {
return errs . Unauthorized ( "authority.authorizeSSHCertificate: certificate has been revoked" , errs . WithKeyVal ( "serialNumber" , serial ) )
}
return nil
}
2019-12-20 21:30:05 +00:00
// authorizeSSHSign loads the provisioner from the token, checks that it has not
// been used again and calls the provisioner AuthorizeSSHSign method. Returns a
// list of methods to apply to the signing flow.
func ( a * Authority ) authorizeSSHSign ( ctx context . Context , token string ) ( [ ] provisioner . SignOption , error ) {
p , err := a . authorizeToken ( ctx , token )
if err != nil {
return nil , errs . Wrap ( http . StatusUnauthorized , err , "authority.authorizeSSHSign" )
}
signOpts , err := p . AuthorizeSSHSign ( ctx , token )
if err != nil {
return nil , errs . Wrap ( http . StatusUnauthorized , err , "authority.authorizeSSHSign" )
}
return signOpts , nil
}
// authorizeSSHRenew authorizes an SSH certificate renewal request, by
// validating the contents of an SSHPOP token.
func ( a * Authority ) authorizeSSHRenew ( ctx context . Context , token string ) ( * ssh . Certificate , error ) {
p , err := a . authorizeToken ( ctx , token )
if err != nil {
return nil , errs . Wrap ( http . StatusInternalServerError , err , "authority.authorizeSSHRenew" )
}
cert , err := p . AuthorizeSSHRenew ( ctx , token )
if err != nil {
return nil , errs . Wrap ( http . StatusInternalServerError , err , "authority.authorizeSSHRenew" )
}
return cert , nil
}
// authorizeSSHRekey authorizes an SSH certificate rekey request, by
// validating the contents of an SSHPOP token.
func ( a * Authority ) authorizeSSHRekey ( ctx context . Context , token string ) ( * ssh . Certificate , [ ] provisioner . SignOption , error ) {
p , err := a . authorizeToken ( ctx , token )
if err != nil {
return nil , nil , errs . Wrap ( http . StatusInternalServerError , err , "authority.authorizeSSHRekey" )
}
cert , signOpts , err := p . AuthorizeSSHRekey ( ctx , token )
if err != nil {
return nil , nil , errs . Wrap ( http . StatusInternalServerError , err , "authority.authorizeSSHRekey" )
}
return cert , signOpts , nil
}
// authorizeSSHRevoke authorizes an SSH certificate revoke request, by
// validating the contents of an SSHPOP token.
func ( a * Authority ) authorizeSSHRevoke ( ctx context . Context , token string ) error {
p , err := a . authorizeToken ( ctx , token )
if err != nil {
return errs . Wrap ( http . StatusInternalServerError , err , "authority.authorizeSSHRevoke" )
}
if err = p . AuthorizeSSHRevoke ( ctx , token ) ; err != nil {
return errs . Wrap ( http . StatusInternalServerError , err , "authority.authorizeSSHRevoke" )
2018-11-01 22:43:24 +00:00
}
return nil
}