-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: allow custom validations on PGP key
Allow custom max allowed key lifetime, clock skew and email validation for the PGP keys. Signed-off-by: Utku Ozdemir <utku.ozdemir@siderolabs.com>
- Loading branch information
1 parent
63d4da3
commit 69886dc
Showing
3 changed files
with
165 additions
and
69 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
// This Source Code Form is subject to the terms of the Mozilla Public | ||
// License, v. 2.0. If a copy of the MPL was not distributed with this | ||
// file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
|
||
package pgp | ||
|
||
import ( | ||
"fmt" | ||
"net/mail" | ||
"time" | ||
) | ||
|
||
// Key validation defaults. | ||
const ( | ||
DefaultMaxAllowedLifetime = 8 * time.Hour | ||
DefaultAllowedClockSkew = 5 * time.Minute | ||
DefaultValidEmailAsName = true | ||
) | ||
|
||
type validationOptions struct { | ||
maxAllowedLifetime time.Duration | ||
validEmailAsName bool | ||
allowedClockSkew time.Duration | ||
} | ||
|
||
func newDefaultValidationOptions() validationOptions { | ||
return validationOptions{ | ||
maxAllowedLifetime: DefaultMaxAllowedLifetime, | ||
allowedClockSkew: DefaultAllowedClockSkew, | ||
validEmailAsName: DefaultValidEmailAsName, | ||
} | ||
} | ||
|
||
// ValidationOption represents a functional validation option. | ||
type ValidationOption func(*validationOptions) | ||
|
||
// WithMaxAllowedLifetime customizes the max allowed key lifetime in the validation. | ||
func WithMaxAllowedLifetime(maxAllowedLifetime time.Duration) ValidationOption { | ||
return func(o *validationOptions) { | ||
o.maxAllowedLifetime = maxAllowedLifetime | ||
} | ||
} | ||
|
||
// WithValidEmailAsName sets whether the validation should be performed on the name to be a valid email address. | ||
func WithValidEmailAsName(validEmailAsName bool) ValidationOption { | ||
return func(o *validationOptions) { | ||
o.validEmailAsName = validEmailAsName | ||
} | ||
} | ||
|
||
// WithAllowedClockSkew sets the allowed clock skew in the key expiration validation. | ||
func WithAllowedClockSkew(allowedClockSkew time.Duration) ValidationOption { | ||
return func(o *validationOptions) { | ||
o.allowedClockSkew = allowedClockSkew | ||
} | ||
} | ||
|
||
// Validate validates the key. | ||
func (p *Key) Validate(opt ...ValidationOption) error { | ||
options := newDefaultValidationOptions() | ||
|
||
for _, o := range opt { | ||
o(&options) | ||
} | ||
|
||
if p.key.IsRevoked() { | ||
return fmt.Errorf("key is revoked") | ||
} | ||
|
||
entity := p.key.GetEntity() | ||
if entity == nil { | ||
return fmt.Errorf("key does not contain an entity") | ||
} | ||
|
||
identity := entity.PrimaryIdentity() | ||
if identity == nil { | ||
return fmt.Errorf("key does not contain a primary identity") | ||
} | ||
|
||
if p.IsExpired(options.allowedClockSkew) { | ||
return fmt.Errorf("key expired") | ||
} | ||
|
||
if options.validEmailAsName { | ||
_, err := mail.ParseAddress(identity.Name) | ||
if err != nil { | ||
return fmt.Errorf("key does not contain a valid email address: %w: %s", err, identity.Name) | ||
} | ||
} | ||
|
||
return p.validateLifetime(&options) | ||
} | ||
|
||
func (p *Key) validateLifetime(opts *validationOptions) error { | ||
entity := p.key.GetEntity() | ||
identity := entity.PrimaryIdentity() | ||
sig := identity.SelfSignature | ||
|
||
if sig.KeyLifetimeSecs == nil || *sig.KeyLifetimeSecs == 0 { | ||
return fmt.Errorf("key does not contain a valid key lifetime") | ||
} | ||
|
||
expiration := time.Now().Add(opts.maxAllowedLifetime) | ||
|
||
if !entity.PrimaryKey.KeyExpired(sig, expiration) { | ||
return fmt.Errorf("key lifetime is too long: %s", time.Duration(*sig.KeyLifetimeSecs)*time.Second) | ||
} | ||
|
||
return nil | ||
} |