mirror of
https://github.com/pomerium/pomerium.git
synced 2025-08-03 16:59:22 +02:00
cryptutil: move to pkg dir, add token generator (#1029)
* cryptutil: move to pkg dir, add token generator * add gitignored files * add tests
This commit is contained in:
parent
b90885b4c1
commit
fae02791f5
48 changed files with 175 additions and 35 deletions
|
@ -9,8 +9,8 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/internal/testutil"
|
||||
"github.com/pomerium/pomerium/pkg/cryptutil"
|
||||
)
|
||||
|
||||
func Test_buildPolicyTransportSocket(t *testing.T) {
|
||||
|
|
|
@ -20,8 +20,8 @@ import (
|
|||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
|
||||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
"github.com/pomerium/pomerium/pkg/cryptutil"
|
||||
)
|
||||
|
||||
var disableExtAuthz *any.Any
|
||||
|
|
|
@ -10,8 +10,8 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/internal/testutil"
|
||||
"github.com/pomerium/pomerium/pkg/cryptutil"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -1,171 +0,0 @@
|
|||
package cryptutil
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CertificateFromBase64 returns an X509 pair from a base64 encoded blob.
|
||||
func CertificateFromBase64(cert, key string) (*tls.Certificate, error) {
|
||||
decodedCert, err := base64.StdEncoding.DecodeString(cert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode certificate cert %v: %w", decodedCert, err)
|
||||
}
|
||||
decodedKey, err := base64.StdEncoding.DecodeString(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode certificate key %v: %w", decodedKey, err)
|
||||
}
|
||||
x509, err := tls.X509KeyPair(decodedCert, decodedKey)
|
||||
return &x509, err
|
||||
}
|
||||
|
||||
// CertificateFromFile given a certificate, and key file path, returns a X509
|
||||
// keypair.
|
||||
func CertificateFromFile(certFile, keyFile string) (*tls.Certificate, error) {
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
return &cert, err
|
||||
}
|
||||
|
||||
// DecodePublicKey decodes a PEM-encoded ECDSA public key.
|
||||
func DecodePublicKey(encodedKey []byte) (*ecdsa.PublicKey, error) {
|
||||
block, _ := pem.Decode(encodedKey)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("cryptutil: decoded nil PEM block")
|
||||
}
|
||||
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ecdsaPub, ok := pub.(*ecdsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, errors.New("cryptutil: data was not an ECDSA public key")
|
||||
}
|
||||
|
||||
return ecdsaPub, nil
|
||||
}
|
||||
|
||||
// EncodePublicKey encodes an ECDSA public key to PEM format.
|
||||
func EncodePublicKey(key *ecdsa.PublicKey) ([]byte, error) {
|
||||
derBytes, err := x509.MarshalPKIXPublicKey(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block := &pem.Block{
|
||||
Type: "PUBLIC KEY",
|
||||
Bytes: derBytes,
|
||||
}
|
||||
|
||||
return pem.EncodeToMemory(block), nil
|
||||
}
|
||||
|
||||
// DecodePrivateKey decodes a PEM-encoded ECDSA private key.
|
||||
func DecodePrivateKey(encodedKey []byte) (*ecdsa.PrivateKey, error) {
|
||||
var skippedTypes []string
|
||||
var block *pem.Block
|
||||
|
||||
for {
|
||||
block, encodedKey = pem.Decode(encodedKey)
|
||||
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("cryptutil: failed to find EC PRIVATE KEY in PEM data after skipping types %v", skippedTypes)
|
||||
}
|
||||
|
||||
if block.Type == "EC PRIVATE KEY" {
|
||||
break
|
||||
} else {
|
||||
skippedTypes = append(skippedTypes, block.Type)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
privKey, err := x509.ParseECPrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return privKey, nil
|
||||
}
|
||||
|
||||
// EncodePrivateKey encodes an ECDSA private key to PEM format.
|
||||
func EncodePrivateKey(key *ecdsa.PrivateKey) ([]byte, error) {
|
||||
derKey, err := x509.MarshalECPrivateKey(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyBlock := &pem.Block{
|
||||
Type: "EC PRIVATE KEY",
|
||||
Bytes: derKey,
|
||||
}
|
||||
|
||||
return pem.EncodeToMemory(keyBlock), nil
|
||||
}
|
||||
|
||||
// GenerateSelfSignedCertificate generates a self-signed TLS certificate.
|
||||
//
|
||||
// mostly copied from https://golang.org/src/crypto/tls/generate_cert.go
|
||||
func GenerateSelfSignedCertificate(domain string) (*tls.Certificate, error) {
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to geneate private key: %w", err)
|
||||
}
|
||||
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate serial number: %w", err)
|
||||
}
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"Pomerium"},
|
||||
},
|
||||
NotBefore: time.Now().Add(-time.Minute * 10),
|
||||
NotAfter: time.Now().Add(time.Hour * 24 * 365),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
if ip := net.ParseIP(domain); ip != nil {
|
||||
template.IPAddresses = append(template.IPAddresses, ip)
|
||||
} else {
|
||||
template.DNSNames = append(template.DNSNames, domain)
|
||||
}
|
||||
|
||||
publicKeyBytes, err := x509.CreateCertificate(rand.Reader,
|
||||
&template, &template,
|
||||
privateKey.Public(), privateKey,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create certificate: %w", err)
|
||||
}
|
||||
|
||||
privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal private key: %w", err)
|
||||
}
|
||||
|
||||
cert, err := tls.X509KeyPair(
|
||||
pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: publicKeyBytes}),
|
||||
pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privateKeyBytes}),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert x509 bytes into tls certificate: %w", err)
|
||||
}
|
||||
|
||||
return &cert, nil
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
package cryptutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// A keypair for NIST P-256 / secp256r1
|
||||
// Generated using:
|
||||
// openssl ecparam -genkey -name prime256v1 -outform PEM
|
||||
var pemECPrivateKeyP256 = `-----BEGIN EC PARAMETERS-----
|
||||
BggqhkjOPQMBBw==
|
||||
-----END EC PARAMETERS-----
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIOI+EZsjyN3jvWJI/KDihFmqTuDpUe/if6f/pgGTBta/oAoGCCqGSM49
|
||||
AwEHoUQDQgAEhhObKJ1r1PcUw+3REd/TbmSZnDvXnFUSTwqQFo5gbfIlP+gvEYba
|
||||
+Rxj2hhqjfzqxIleRK40IRyEi3fJM/8Qhg==
|
||||
-----END EC PRIVATE KEY-----
|
||||
`
|
||||
|
||||
var pemECPublicKeyP256 = `-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhhObKJ1r1PcUw+3REd/TbmSZnDvX
|
||||
nFUSTwqQFo5gbfIlP+gvEYba+Rxj2hhqjfzqxIleRK40IRyEi3fJM/8Qhg==
|
||||
-----END PUBLIC KEY-----
|
||||
`
|
||||
|
||||
var garbagePEM = `-----BEGIN GARBAGE-----
|
||||
TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ=
|
||||
-----END GARBAGE-----
|
||||
`
|
||||
|
||||
func TestCertifcateFromBase64(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
cert string
|
||||
key string
|
||||
// want *tls.Certificate
|
||||
wantErr bool
|
||||
}{
|
||||
{"good",
|
||||
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVJVENDQWdtZ0F3SUJBZ0lSQVBqTEJxS1lwcWU0ekhQc0dWdFR6T0F3RFFZSktvWklodmNOQVFFTEJRQXcKRWpFUU1BNEdBMVVFQXhNSFoyOXZaQzFqWVRBZUZ3MHhPVEE0TVRBeE9EUTVOREJhRncweU1UQXlNVEF4TnpRdwpNREZhTUJNeEVUQVBCZ05WQkFNVENIQnZiV1Z5YVhWdE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBCk1JSUJDZ0tDQVFFQTY3S2pxbVFZR3EwTVZ0QUNWcGVDbVhtaW5sUWJEUEdMbXNaQVVFd3VlSFFucnQzV3R2cEQKT202QWxhSk1VblcrSHU1NWpqb2thbEtlVmpUS21nWUdicVV6VkRvTWJQRGFIZWtsdGRCVE1HbE9VRnNQNFVKUwpEck80emROK3pvNDI4VFgyUG5HMkZDZFZLR3k0UEU4aWxIYldMY3I4NzFZalY1MWZ3OENMRFg5UFpKTnU4NjFDCkY3VjlpRUptNnNTZlFsbW5oTjhqMytXelZiUFFOeTFXc1I3aTllOWo2M0VxS3QyMlE5T1hMK1dBY0tza29JU20KQ05WUlVBalU4WVJWY2dRSkIrelEzNEFRUGx6ME9wNU8vUU4vTWVkamFGOHdMUytpdi96dmlTOGNxUGJ4bzZzTApxNkZOVGx0ay9Ra3hlQ2VLS1RRZS8za1BZdlFBZG5sNjVRSURBUUFCbzNFd2J6QU9CZ05WSFE4QkFmOEVCQU1DCkE3Z3dIUVlEVlIwbEJCWXdGQVlJS3dZQkJRVUhBd0VHQ0NzR0FRVUZCd01DTUIwR0ExVWREZ1FXQkJRQ1FYbWIKc0hpcS9UQlZUZVhoQ0dpNjhrVy9DakFmQmdOVkhTTUVHREFXZ0JSNTRKQ3pMRlg0T0RTQ1J0dWNBUGZOdVhWegpuREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBZ0VBcm9XL2trMllleFN5NEhaQXFLNDVZaGQ5ay9QVTFiaDlFK1BRCk5jZFgzTUdEY2NDRUFkc1k4dll3NVE1cnhuMGFzcSt3VGFCcGxoYS9rMi9VVW9IQ1RqUVp1Mk94dEF3UTdPaWIKVE1tMEorU3NWT3d4YnFQTW9rK1RqVE16NFdXaFFUTzVwRmNoZDZXZXNCVHlJNzJ0aG1jcDd1c2NLU2h3YktIegpQY2h1QTQ4SzhPdi96WkxmZnduQVNZb3VCczJjd1ZiRDI3ZXZOMzdoMGFzR1BrR1VXdm1PSDduTHNVeTh3TTdqCkNGL3NwMmJmTC9OYVdNclJnTHZBMGZMS2pwWTQrVEpPbkVxQmxPcCsrbHlJTEZMcC9qMHNybjRNUnlKK0t6UTEKR1RPakVtQ1QvVEFtOS9XSThSL0FlYjcwTjEzTytYNEtaOUJHaDAxTzN3T1Vqd3BZZ3lxSnNoRnNRUG50VmMrSQpKQmF4M2VQU3NicUcwTFkzcHdHUkpRNmMrd1lxdGk2Y0tNTjliYlRkMDhCNUk1N1RRTHhNcUoycTFnWmw1R1VUCmVFZGNWRXltMnZmd0NPd0lrbGNBbThxTm5kZGZKV1FabE5VaHNOVWFBMkVINnlDeXdaZm9aak9hSDEwTXowV20KeTNpZ2NSZFQ3Mi9NR2VkZk93MlV0MVVvRFZmdEcxcysrditUQ1lpNmpUQU05dkZPckJ4UGlOeGFkUENHR2NZZAowakZIc2FWOGFPV1dQQjZBQ1JteHdDVDdRTnRTczM2MlpIOUlFWWR4Q00yMDUrZmluVHhkOUcwSmVRRTd2Kyt6CldoeWo2ZmJBWUIxM2wvN1hkRnpNSW5BOGxpekdrVHB2RHMxeTBCUzlwV3ppYmhqbVFoZGZIejdCZGpGTHVvc2wKZzlNZE5sND0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=",
|
||||
"LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcGdJQkFBS0NBUUVBNjdLanFtUVlHcTBNVnRBQ1ZwZUNtWG1pbmxRYkRQR0xtc1pBVUV3dWVIUW5ydDNXCnR2cERPbTZBbGFKTVVuVytIdTU1ampva2FsS2VWalRLbWdZR2JxVXpWRG9NYlBEYUhla2x0ZEJUTUdsT1VGc1AKNFVKU0RyTzR6ZE4rem80MjhUWDJQbkcyRkNkVktHeTRQRThpbEhiV0xjcjg3MVlqVjUxZnc4Q0xEWDlQWkpOdQo4NjFDRjdWOWlFSm02c1NmUWxtbmhOOGozK1d6VmJQUU55MVdzUjdpOWU5ajYzRXFLdDIyUTlPWEwrV0FjS3NrCm9JU21DTlZSVUFqVThZUlZjZ1FKQit6UTM0QVFQbHowT3A1Ty9RTi9NZWRqYUY4d0xTK2l2L3p2aVM4Y3FQYngKbzZzTHE2Rk5UbHRrL1FreGVDZUtLVFFlLzNrUFl2UUFkbmw2NVFJREFRQUJBb0lCQVFEQVQ0eXN2V2pSY3pxcgpKcU9SeGFPQTJEY3dXazJML1JXOFhtQWhaRmRTWHV2MkNQbGxhTU1yelBmTG41WUlmaHQzSDNzODZnSEdZc3pnClo4aWJiYWtYNUdFQ0t5N3lRSDZuZ3hFS3pRVGpiampBNWR3S0h0UFhQUnJmamQ1Y2FMczVpcDcxaWxCWEYxU3IKWERIaXUycnFtaC9kVTArWGRMLzNmK2VnVDl6bFQ5YzRyUm84dnZueWNYejFyMnVhRVZ2VExsWHVsb2NpeEVrcgoySjlTMmxveWFUb2tFTnNlMDNpSVdaWnpNNElZcVowOGJOeG9IWCszQXVlWExIUStzRkRKMlhaVVdLSkZHMHUyClp3R2w3YlZpRTFQNXdiQUdtZzJDeDVCN1MrdGQyUEpSV3Frb2VxY3F2RVdCc3RFL1FEcDFpVThCOHpiQXd0Y3IKZHc5TXZ6Q2hBb0dCQVBObzRWMjF6MGp6MWdEb2tlTVN5d3JnL2E4RkJSM2R2Y0xZbWV5VXkybmd3eHVucnFsdwo2U2IrOWdrOGovcXEvc3VQSDhVdzNqSHNKYXdGSnNvTkVqNCt2b1ZSM3UrbE5sTEw5b21rMXBoU0dNdVp0b3huCm5nbUxVbkJUMGI1M3BURkJ5WGsveE5CbElreWdBNlg5T2MreW5na3RqNlRyVnMxUERTdnVJY0s1QW9HQkFQZmoKcEUzR2F6cVFSemx6TjRvTHZmQWJBdktCZ1lPaFNnemxsK0ZLZkhzYWJGNkdudFd1dWVhY1FIWFpYZTA1c2tLcApXN2xYQ3dqQU1iUXI3QmdlazcrOSszZElwL1RnYmZCYnN3Syt6Vng3Z2doeWMrdytXRWExaHByWTZ6YXdxdkFaCkhRU2lMUEd1UGp5WXBQa1E2ZFdEczNmWHJGZ1dlTmd4SkhTZkdaT05Bb0dCQUt5WTF3MUM2U3Y2c3VuTC8vNTcKQ2Z5NTAwaXlqNUZBOWRqZkRDNWt4K1JZMnlDV0ExVGsybjZyVmJ6dzg4czBTeDMrYS9IQW1CM2dMRXBSRU5NKwo5NHVwcENFWEQ3VHdlcGUxUnlrTStKbmp4TzlDSE41c2J2U25sUnBQWlMvZzJRTVhlZ3grK2trbkhXNG1ITkFyCndqMlRrMXBBczFXbkJ0TG9WaGVyY01jSkFvR0JBSTYwSGdJb0Y5SysvRUcyY21LbUg5SDV1dGlnZFU2eHEwK0IKWE0zMWMzUHE0amdJaDZlN3pvbFRxa2d0dWtTMjBraE45dC9ibkI2TmhnK1N1WGVwSXFWZldVUnlMejVwZE9ESgo2V1BMTTYzcDdCR3cwY3RPbU1NYi9VRm5Yd0U4OHlzRlNnOUF6VjdVVUQvU0lDYkI5ZHRVMWh4SHJJK0pZRWdWCkFrZWd6N2lCQW9HQkFJRncrQVFJZUIwM01UL0lCbGswNENQTDJEak0rNDhoVGRRdjgwMDBIQU9mUWJrMEVZUDEKQ2FLR3RDbTg2MXpBZjBzcS81REtZQ0l6OS9HUzNYRk00Qm1rRk9nY1NXVENPNmZmTGdLM3FmQzN4WDJudlpIOQpYZGNKTDQrZndhY0x4c2JJKzhhUWNOVHRtb3pkUjEzQnNmUmIrSGpUL2o3dkdrYlFnSkhCT0syegotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=",
|
||||
false},
|
||||
{"bad cert",
|
||||
"!=",
|
||||
"LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcGdJQkFBS0NBUUVBNjdLanFtUVlHcTBNVnRBQ1ZwZUNtWG1pbmxRYkRQR0xtc1pBVUV3dWVIUW5ydDNXCnR2cERPbTZBbGFKTVVuVytIdTU1ampva2FsS2VWalRLbWdZR2JxVXpWRG9NYlBEYUhla2x0ZEJUTUdsT1VGc1AKNFVKU0RyTzR6ZE4rem80MjhUWDJQbkcyRkNkVktHeTRQRThpbEhiV0xjcjg3MVlqVjUxZnc4Q0xEWDlQWkpOdQo4NjFDRjdWOWlFSm02c1NmUWxtbmhOOGozK1d6VmJQUU55MVdzUjdpOWU5ajYzRXFLdDIyUTlPWEwrV0FjS3NrCm9JU21DTlZSVUFqVThZUlZjZ1FKQit6UTM0QVFQbHowT3A1Ty9RTi9NZWRqYUY4d0xTK2l2L3p2aVM4Y3FQYngKbzZzTHE2Rk5UbHRrL1FreGVDZUtLVFFlLzNrUFl2UUFkbmw2NVFJREFRQUJBb0lCQVFEQVQ0eXN2V2pSY3pxcgpKcU9SeGFPQTJEY3dXazJML1JXOFhtQWhaRmRTWHV2MkNQbGxhTU1yelBmTG41WUlmaHQzSDNzODZnSEdZc3pnClo4aWJiYWtYNUdFQ0t5N3lRSDZuZ3hFS3pRVGpiampBNWR3S0h0UFhQUnJmamQ1Y2FMczVpcDcxaWxCWEYxU3IKWERIaXUycnFtaC9kVTArWGRMLzNmK2VnVDl6bFQ5YzRyUm84dnZueWNYejFyMnVhRVZ2VExsWHVsb2NpeEVrcgoySjlTMmxveWFUb2tFTnNlMDNpSVdaWnpNNElZcVowOGJOeG9IWCszQXVlWExIUStzRkRKMlhaVVdLSkZHMHUyClp3R2w3YlZpRTFQNXdiQUdtZzJDeDVCN1MrdGQyUEpSV3Frb2VxY3F2RVdCc3RFL1FEcDFpVThCOHpiQXd0Y3IKZHc5TXZ6Q2hBb0dCQVBObzRWMjF6MGp6MWdEb2tlTVN5d3JnL2E4RkJSM2R2Y0xZbWV5VXkybmd3eHVucnFsdwo2U2IrOWdrOGovcXEvc3VQSDhVdzNqSHNKYXdGSnNvTkVqNCt2b1ZSM3UrbE5sTEw5b21rMXBoU0dNdVp0b3huCm5nbUxVbkJUMGI1M3BURkJ5WGsveE5CbElreWdBNlg5T2MreW5na3RqNlRyVnMxUERTdnVJY0s1QW9HQkFQZmoKcEUzR2F6cVFSemx6TjRvTHZmQWJBdktCZ1lPaFNnemxsK0ZLZkhzYWJGNkdudFd1dWVhY1FIWFpYZTA1c2tLcApXN2xYQ3dqQU1iUXI3QmdlazcrOSszZElwL1RnYmZCYnN3Syt6Vng3Z2doeWMrdytXRWExaHByWTZ6YXdxdkFaCkhRU2lMUEd1UGp5WXBQa1E2ZFdEczNmWHJGZ1dlTmd4SkhTZkdaT05Bb0dCQUt5WTF3MUM2U3Y2c3VuTC8vNTcKQ2Z5NTAwaXlqNUZBOWRqZkRDNWt4K1JZMnlDV0ExVGsybjZyVmJ6dzg4czBTeDMrYS9IQW1CM2dMRXBSRU5NKwo5NHVwcENFWEQ3VHdlcGUxUnlrTStKbmp4TzlDSE41c2J2U25sUnBQWlMvZzJRTVhlZ3grK2trbkhXNG1ITkFyCndqMlRrMXBBczFXbkJ0TG9WaGVyY01jSkFvR0JBSTYwSGdJb0Y5SysvRUcyY21LbUg5SDV1dGlnZFU2eHEwK0IKWE0zMWMzUHE0amdJaDZlN3pvbFRxa2d0dWtTMjBraE45dC9ibkI2TmhnK1N1WGVwSXFWZldVUnlMejVwZE9ESgo2V1BMTTYzcDdCR3cwY3RPbU1NYi9VRm5Yd0U4OHlzRlNnOUF6VjdVVUQvU0lDYkI5ZHRVMWh4SHJJK0pZRWdWCkFrZWd6N2lCQW9HQkFJRncrQVFJZUIwM01UL0lCbGswNENQTDJEak0rNDhoVGRRdjgwMDBIQU9mUWJrMEVZUDEKQ2FLR3RDbTg2MXpBZjBzcS81REtZQ0l6OS9HUzNYRk00Qm1rRk9nY1NXVENPNmZmTGdLM3FmQzN4WDJudlpIOQpYZGNKTDQrZndhY0x4c2JJKzhhUWNOVHRtb3pkUjEzQnNmUmIrSGpUL2o3dkdrYlFnSkhCT0syegotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=",
|
||||
true},
|
||||
{"bad key",
|
||||
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVJVENDQWdtZ0F3SUJBZ0lSQVBqTEJxS1lwcWU0ekhQc0dWdFR6T0F3RFFZSktvWklodmNOQVFFTEJRQXcKRWpFUU1BNEdBMVVFQXhNSFoyOXZaQzFqWVRBZUZ3MHhPVEE0TVRBeE9EUTVOREJhRncweU1UQXlNVEF4TnpRdwpNREZhTUJNeEVUQVBCZ05WQkFNVENIQnZiV1Z5YVhWdE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBCk1JSUJDZ0tDQVFFQTY3S2pxbVFZR3EwTVZ0QUNWcGVDbVhtaW5sUWJEUEdMbXNaQVVFd3VlSFFucnQzV3R2cEQKT202QWxhSk1VblcrSHU1NWpqb2thbEtlVmpUS21nWUdicVV6VkRvTWJQRGFIZWtsdGRCVE1HbE9VRnNQNFVKUwpEck80emROK3pvNDI4VFgyUG5HMkZDZFZLR3k0UEU4aWxIYldMY3I4NzFZalY1MWZ3OENMRFg5UFpKTnU4NjFDCkY3VjlpRUptNnNTZlFsbW5oTjhqMytXelZiUFFOeTFXc1I3aTllOWo2M0VxS3QyMlE5T1hMK1dBY0tza29JU20KQ05WUlVBalU4WVJWY2dRSkIrelEzNEFRUGx6ME9wNU8vUU4vTWVkamFGOHdMUytpdi96dmlTOGNxUGJ4bzZzTApxNkZOVGx0ay9Ra3hlQ2VLS1RRZS8za1BZdlFBZG5sNjVRSURBUUFCbzNFd2J6QU9CZ05WSFE4QkFmOEVCQU1DCkE3Z3dIUVlEVlIwbEJCWXdGQVlJS3dZQkJRVUhBd0VHQ0NzR0FRVUZCd01DTUIwR0ExVWREZ1FXQkJRQ1FYbWIKc0hpcS9UQlZUZVhoQ0dpNjhrVy9DakFmQmdOVkhTTUVHREFXZ0JSNTRKQ3pMRlg0T0RTQ1J0dWNBUGZOdVhWegpuREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBZ0VBcm9XL2trMllleFN5NEhaQXFLNDVZaGQ5ay9QVTFiaDlFK1BRCk5jZFgzTUdEY2NDRUFkc1k4dll3NVE1cnhuMGFzcSt3VGFCcGxoYS9rMi9VVW9IQ1RqUVp1Mk94dEF3UTdPaWIKVE1tMEorU3NWT3d4YnFQTW9rK1RqVE16NFdXaFFUTzVwRmNoZDZXZXNCVHlJNzJ0aG1jcDd1c2NLU2h3YktIegpQY2h1QTQ4SzhPdi96WkxmZnduQVNZb3VCczJjd1ZiRDI3ZXZOMzdoMGFzR1BrR1VXdm1PSDduTHNVeTh3TTdqCkNGL3NwMmJmTC9OYVdNclJnTHZBMGZMS2pwWTQrVEpPbkVxQmxPcCsrbHlJTEZMcC9qMHNybjRNUnlKK0t6UTEKR1RPakVtQ1QvVEFtOS9XSThSL0FlYjcwTjEzTytYNEtaOUJHaDAxTzN3T1Vqd3BZZ3lxSnNoRnNRUG50VmMrSQpKQmF4M2VQU3NicUcwTFkzcHdHUkpRNmMrd1lxdGk2Y0tNTjliYlRkMDhCNUk1N1RRTHhNcUoycTFnWmw1R1VUCmVFZGNWRXltMnZmd0NPd0lrbGNBbThxTm5kZGZKV1FabE5VaHNOVWFBMkVINnlDeXdaZm9aak9hSDEwTXowV20KeTNpZ2NSZFQ3Mi9NR2VkZk93MlV0MVVvRFZmdEcxcysrditUQ1lpNmpUQU05dkZPckJ4UGlOeGFkUENHR2NZZAowakZIc2FWOGFPV1dQQjZBQ1JteHdDVDdRTnRTczM2MlpIOUlFWWR4Q00yMDUrZmluVHhkOUcwSmVRRTd2Kyt6CldoeWo2ZmJBWUIxM2wvN1hkRnpNSW5BOGxpekdrVHB2RHMxeTBCUzlwV3ppYmhqbVFoZGZIejdCZGpGTHVvc2wKZzlNZE5sND0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=",
|
||||
"!=",
|
||||
true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := CertificateFromBase64(tt.cert, tt.key)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("CertificateFromBase64() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCertificateFromFile(t *testing.T) {
|
||||
cert, err := CertificateFromFile("testdata/example-cert.pem", "testdata/example-key.pem")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cfg := &tls.Config{Certificates: []tls.Certificate{*cert}}
|
||||
listener, err := tls.Listen("tcp", ":2000", cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = listener
|
||||
}
|
||||
|
||||
func TestPublicKeyMarshaling(t *testing.T) {
|
||||
ecKey, err := DecodePublicKey([]byte(pemECPublicKeyP256))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = DecodePublicKey(nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
|
||||
pemBytes, _ := EncodePublicKey(ecKey)
|
||||
if !bytes.Equal(pemBytes, []byte(pemECPublicKeyP256)) {
|
||||
t.Fatal("public key encoding did not match")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestPrivateKeyBadDecode(t *testing.T) {
|
||||
_, err := DecodePrivateKey([]byte(garbagePEM))
|
||||
if err == nil {
|
||||
t.Fatal("decoded garbage data without complaint")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrivateKeyMarshaling(t *testing.T) {
|
||||
ecKey, err := DecodePrivateKey([]byte(pemECPrivateKeyP256))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pemBytes, _ := EncodePrivateKey(ecKey)
|
||||
if !strings.HasSuffix(pemECPrivateKeyP256, string(pemBytes)) {
|
||||
t.Fatal("private key encoding did not match")
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
// Package cryptutil provides cryptographic utility functions, complementing the
|
||||
// lower level abstractions found in the standard library.
|
||||
package cryptutil
|
|
@ -1,51 +0,0 @@
|
|||
package cryptutil
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
// NewAEADCipher takes secret key and returns a new XChacha20poly1305 cipher.
|
||||
func NewAEADCipher(secret []byte) (cipher.AEAD, error) {
|
||||
if len(secret) != 32 {
|
||||
return nil, fmt.Errorf("cryptutil: got %d bytes but want 32", len(secret))
|
||||
}
|
||||
return chacha20poly1305.NewX(secret)
|
||||
|
||||
}
|
||||
|
||||
// NewAEADCipherFromBase64 takes a base64 encoded secret key and returns a new XChacha20poly1305 cipher.
|
||||
func NewAEADCipherFromBase64(s string) (cipher.AEAD, error) {
|
||||
decoded, err := base64.StdEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cryptutil: invalid base64: %w", err)
|
||||
}
|
||||
return NewAEADCipher(decoded)
|
||||
}
|
||||
|
||||
// Encrypt encrypts a value with optional associated data
|
||||
//
|
||||
// Panics if source of randomness fails.
|
||||
func Encrypt(a cipher.AEAD, data, ad []byte) []byte {
|
||||
iv := randomBytes(a.NonceSize())
|
||||
ciphertext := a.Seal(nil, iv, data, ad)
|
||||
return append(ciphertext, iv...)
|
||||
}
|
||||
|
||||
// Decrypt a value with optional associated data
|
||||
func Decrypt(a cipher.AEAD, data, ad []byte) ([]byte, error) {
|
||||
if len(data) <= a.NonceSize() {
|
||||
return nil, fmt.Errorf("cryptutil: invalid input size: %d", len(data))
|
||||
}
|
||||
size := len(data) - a.NonceSize()
|
||||
ciphertext := data[:size]
|
||||
nonce := data[size:]
|
||||
plaintext, err := a.Open(nil, nonce, ciphertext, ad)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return plaintext, nil
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
package cryptutil
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEncodeAndDecodeAccessToken(t *testing.T) {
|
||||
plaintext := []byte("my plain text value")
|
||||
|
||||
key := NewKey()
|
||||
c, err := NewAEADCipher(key)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
|
||||
ciphertext := Encrypt(c, plaintext, nil)
|
||||
|
||||
if reflect.DeepEqual(plaintext, ciphertext) {
|
||||
t.Fatalf("plaintext is not encrypted plaintext:%v ciphertext:%x", plaintext, ciphertext)
|
||||
}
|
||||
|
||||
got, err := Decrypt(c, ciphertext, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err decrypting: %v", err)
|
||||
}
|
||||
|
||||
// if less than 32 bytes, fail
|
||||
_, err = Decrypt(c, []byte("oh"), nil)
|
||||
if err == nil {
|
||||
t.Fatalf("should fail if <32 bytes output: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, plaintext) {
|
||||
t.Logf(" got: %v", got)
|
||||
t.Logf("want: %v", plaintext)
|
||||
t.Fatal("got unexpected decrypted value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewAEADCipher(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
secret []byte
|
||||
wantErr bool
|
||||
}{
|
||||
{"simple 32 byte key", NewKey(), false},
|
||||
{"key too short", []byte("what is entropy"), true},
|
||||
{"key too long", []byte(NewRandomStringN(33)), true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := NewAEADCipher(tt.secret)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("NewAEADCipher() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewAEADCipherFromBase64(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
s string
|
||||
wantErr bool
|
||||
}{
|
||||
{"simple 32 byte key", base64.StdEncoding.EncodeToString(NewKey()), false},
|
||||
{"key too short", base64.StdEncoding.EncodeToString([]byte("what is entropy")), true},
|
||||
{"key too long", NewRandomStringN(33), true},
|
||||
{"bad base 64", string(NewKey()), true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := NewAEADCipherFromBase64(tt.s)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("NewAEADCipherFromBase64() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
package cryptutil
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha512"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// Hash generates a hash of data using HMAC-SHA-512/256. The tag is intended to
|
||||
// be a natural-language string describing the purpose of the hash, such as
|
||||
// "hash file for lookup key" or "master secret to client secret". It serves
|
||||
// as an HMAC "key" and ensures that different purposes will have different
|
||||
// hash output. This function is NOT suitable for hashing passwords.
|
||||
func Hash(tag string, data []byte) []byte {
|
||||
h := hmac.New(sha512.New512_256, []byte(tag))
|
||||
h.Write(data)
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
// HashPassword generates a bcrypt hash of the password using work factor 14.
|
||||
func HashPassword(password []byte) ([]byte, error) {
|
||||
return bcrypt.GenerateFromPassword(password, 14)
|
||||
}
|
||||
|
||||
// CheckPasswordHash securely compares a bcrypt hashed password with its possible
|
||||
// plaintext equivalent. Returns nil on success, or an error on failure.
|
||||
func CheckPasswordHash(hash, password []byte) error {
|
||||
return bcrypt.CompareHashAndPassword(hash, password)
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
package cryptutil
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPasswordHashing(t *testing.T) {
|
||||
t.Parallel()
|
||||
bcryptTests := []struct {
|
||||
plaintext []byte
|
||||
hash []byte
|
||||
}{
|
||||
{
|
||||
plaintext: []byte("password"),
|
||||
hash: []byte("$2a$14$uALAQb/Lwl59oHVbuUa5m.xEFmQBc9ME/IiSgJK/VHtNJJXASCDoS"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range bcryptTests {
|
||||
hashed, err := HashPassword(tt.plaintext)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if err = CheckPasswordHash(hashed, tt.plaintext); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmarks SHA256 on 16K of random data.
|
||||
func BenchmarkSHA256(b *testing.B) {
|
||||
data, err := ioutil.ReadFile("testdata/random")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.SetBytes(int64(len(data)))
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = sha256.Sum256(data)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmarks SHA512/256 on 16K of random data.
|
||||
func BenchmarkSHA512_256(b *testing.B) {
|
||||
data, err := ioutil.ReadFile("testdata/random")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.SetBytes(int64(len(data)))
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = sha512.Sum512_256(data)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBcrypt(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := HashPassword([]byte("thisisareallybadpassword"))
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleHash() {
|
||||
tag := "hashing file for lookup key"
|
||||
contents, err := ioutil.ReadFile("testdata/random")
|
||||
if err != nil {
|
||||
fmt.Printf("could not read file: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
digest := Hash(tag, contents)
|
||||
fmt.Println(hex.EncodeToString(digest))
|
||||
// Output: 9f4c795d8ae5c207f19184ccebee6a606c1fdfe509c793614066d613580f03e1
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
package cryptutil
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
// DefaultKeySize is the default key size in bytes.
|
||||
const DefaultKeySize = 32
|
||||
|
||||
// NewKey generates a random 32-byte (256 bit) key.
|
||||
//
|
||||
// Panics if source of randomness fails.
|
||||
func NewKey() []byte {
|
||||
return randomBytes(DefaultKeySize)
|
||||
}
|
||||
|
||||
// NewBase64Key generates a random base64 encoded 32-byte key.
|
||||
//
|
||||
// Panics if source of randomness fails.
|
||||
func NewBase64Key() string {
|
||||
return NewRandomStringN(DefaultKeySize)
|
||||
}
|
||||
|
||||
// NewRandomStringN returns base64 encoded random string of a given num of bytes.
|
||||
//
|
||||
// Panics if source of randomness fails.
|
||||
func NewRandomStringN(c int) string {
|
||||
return base64.StdEncoding.EncodeToString(randomBytes(c))
|
||||
}
|
||||
|
||||
// randomBytes generates C number of random bytes suitable for cryptographic
|
||||
// operations.
|
||||
//
|
||||
// Panics if source of randomness fails.
|
||||
func randomBytes(c int) []byte {
|
||||
if c < 0 {
|
||||
c = DefaultKeySize
|
||||
}
|
||||
b := make([]byte, c)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
package cryptutil
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGenerateRandomString(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
c int
|
||||
want int
|
||||
}{
|
||||
{"simple", 32, 32},
|
||||
{"zero", 0, 0},
|
||||
{"negative", -1, 32},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := NewRandomStringN(tt.c)
|
||||
b, err := base64.StdEncoding.DecodeString(o)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
got := len(b)
|
||||
if got != tt.want {
|
||||
t.Errorf("NewRandomStringN() = %d, want %d", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewBase64Key(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
want int
|
||||
}{
|
||||
{"simple", 32},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := NewBase64Key()
|
||||
b, err := base64.StdEncoding.DecodeString(o)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
got := len(b)
|
||||
if got != tt.want {
|
||||
t.Errorf("NewBase64Key() = %d, want %d", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
package cryptutil
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha512"
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultLeeway defines the default leeway for matching NotBefore/Expiry claims.
|
||||
DefaultLeeway = 5.0 * time.Minute
|
||||
)
|
||||
|
||||
var (
|
||||
errTimestampMalformed = errors.New("internal/cryptutil: timestamp malformed")
|
||||
errTimestampExpired = errors.New("internal/cryptutil: timestamp expired")
|
||||
errTimestampTooSoon = errors.New("internal/cryptutil: timestamp too soon")
|
||||
)
|
||||
|
||||
// GenerateHMAC produces a symmetric signature using a shared secret key.
|
||||
func GenerateHMAC(data []byte, key string) []byte {
|
||||
h := hmac.New(sha512.New512_256, []byte(key))
|
||||
h.Write(data)
|
||||
return h.Sum(nil)
|
||||
|
||||
}
|
||||
|
||||
// CheckHMAC securely checks the supplied MAC against a message using the
|
||||
// shared secret key.
|
||||
func CheckHMAC(data, suppliedMAC []byte, key string) bool {
|
||||
expectedMAC := GenerateHMAC(data, key)
|
||||
return hmac.Equal(expectedMAC, suppliedMAC)
|
||||
}
|
||||
|
||||
// ValidTimestamp is a helper function often used in conjunction with an HMAC
|
||||
// function to verify that the timestamp (in unix seconds) is within leeway
|
||||
// period.
|
||||
func ValidTimestamp(ts string) error {
|
||||
var timeStamp int64
|
||||
var err error
|
||||
if timeStamp, err = strconv.ParseInt(ts, 10, 64); err != nil {
|
||||
return errTimestampMalformed
|
||||
}
|
||||
// unix time in seconds
|
||||
tm := time.Unix(timeStamp, 0)
|
||||
if time.Since(tm) > DefaultLeeway {
|
||||
return errTimestampExpired
|
||||
}
|
||||
if time.Until(tm) > DefaultLeeway {
|
||||
return errTimestampTooSoon
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
package cryptutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestHMAC(t *testing.T) {
|
||||
// https://groups.google.com/d/msg/sci.crypt/OolWgsgQD-8/jHciyWkaL0gJ
|
||||
hmacTests := []struct {
|
||||
key string
|
||||
data string
|
||||
digest string
|
||||
}{
|
||||
{
|
||||
key: "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
|
||||
data: "4869205468657265", // "Hi There"
|
||||
digest: "9f9126c3d9c3c330d760425ca8a217e31feae31bfe70196ff81642b868402eab",
|
||||
},
|
||||
{
|
||||
key: "4a656665", // "Jefe"
|
||||
data: "7768617420646f2079612077616e7420666f72206e6f7468696e673f", // "what do ya want for nothing?"
|
||||
digest: "6df7b24630d5ccb2ee335407081a87188c221489768fa2020513b2d593359456",
|
||||
},
|
||||
}
|
||||
for idx, tt := range hmacTests {
|
||||
keySlice, _ := hex.DecodeString(tt.key)
|
||||
dataBytes, _ := hex.DecodeString(tt.data)
|
||||
expectedDigest, _ := hex.DecodeString(tt.digest)
|
||||
|
||||
keyBytes := &[32]byte{}
|
||||
copy(keyBytes[:], keySlice)
|
||||
|
||||
macDigest := GenerateHMAC(dataBytes, string(keyBytes[:]))
|
||||
if !bytes.Equal(macDigest, expectedDigest) {
|
||||
t.Errorf("test %d generated unexpected mac", idx)
|
||||
}
|
||||
if !CheckHMAC(dataBytes, macDigest, string(keyBytes[:])) {
|
||||
t.Errorf("test %d generated unexpected mac", idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidTimestamp(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
ts string
|
||||
wantErr bool
|
||||
}{
|
||||
{"good - now", fmt.Sprint(time.Now().Unix()), false},
|
||||
{"good - now - 200ms", fmt.Sprint(time.Now().Add(-200 * time.Millisecond).Unix()), false},
|
||||
{"good - now + 200ms", fmt.Sprint(time.Now().Add(200 * time.Millisecond).Unix()), false},
|
||||
{"bad - now + 10m", fmt.Sprint(time.Now().Add(10 * time.Minute).Unix()), true},
|
||||
{"bad - now - 10m", fmt.Sprint(time.Now().Add(-10 * time.Minute).Unix()), true},
|
||||
{"malformed - non int", fmt.Sprint("pomerium"), true},
|
||||
{"malformed - negative number", fmt.Sprint("-1"), true},
|
||||
{"malformed - huge number", fmt.Sprintf("%d", 10*10000000000), true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := ValidTimestamp(tt.ts); (err != nil) != tt.wantErr {
|
||||
t.Errorf("ValidTimestamp() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
package cryptutil
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
// PrivateJWKFromBytes returns a jose JSON Web _Private_ Key from bytes.
|
||||
func PrivateJWKFromBytes(data []byte, alg jose.SignatureAlgorithm) (*jose.JSONWebKey, error) {
|
||||
return loadKey(data, alg, func(b []byte) (interface{}, error) {
|
||||
switch alg {
|
||||
case jose.ES256, jose.ES384, jose.ES512:
|
||||
return x509.ParseECPrivateKey(b)
|
||||
case jose.RS256, jose.RS384, jose.RS512:
|
||||
return x509.ParsePKCS1PrivateKey(b)
|
||||
default:
|
||||
return nil, errors.New("unsupported signature algorithm")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// PublicJWKFromBytes returns a jose JSON Web _Public_ Key from bytes.
|
||||
func PublicJWKFromBytes(data []byte, alg jose.SignatureAlgorithm) (*jose.JSONWebKey, error) {
|
||||
return loadKey(data, alg, func(b []byte) (interface{}, error) {
|
||||
switch alg {
|
||||
case jose.ES256, jose.ES384, jose.ES512:
|
||||
key, err := x509.ParseECPrivateKey(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return key.Public(), nil
|
||||
case jose.RS256, jose.RS384, jose.RS512:
|
||||
key, err := x509.ParsePKCS1PrivateKey(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return key.Public(), nil
|
||||
default:
|
||||
return nil, errors.New("unsupported signature algorithm")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func loadKey(data []byte, alg jose.SignatureAlgorithm, unmarshal func([]byte) (interface{}, error)) (*jose.JSONWebKey, error) {
|
||||
block, _ := pem.Decode(data)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("file contained no PEM encoded data")
|
||||
}
|
||||
priv, err := unmarshal(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshal key: %w", err)
|
||||
}
|
||||
key := &jose.JSONWebKey{Key: priv, Use: "sig", Algorithm: string(alg)}
|
||||
thumbprint, err := key.Thumbprint(crypto.SHA256)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("computing thumbprint: %w", err)
|
||||
}
|
||||
key.KeyID = hex.EncodeToString(thumbprint)
|
||||
return key, nil
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
package cryptutil
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
func TestPrivateJWKFromBytes(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
cert string
|
||||
alg jose.SignatureAlgorithm
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{"good RS256",
|
||||
"LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcGdJQkFBS0NBUUVBNjdLanFtUVlHcTBNVnRBQ1ZwZUNtWG1pbmxRYkRQR0xtc1pBVUV3dWVIUW5ydDNXCnR2cERPbTZBbGFKTVVuVytIdTU1ampva2FsS2VWalRLbWdZR2JxVXpWRG9NYlBEYUhla2x0ZEJUTUdsT1VGc1AKNFVKU0RyTzR6ZE4rem80MjhUWDJQbkcyRkNkVktHeTRQRThpbEhiV0xjcjg3MVlqVjUxZnc4Q0xEWDlQWkpOdQo4NjFDRjdWOWlFSm02c1NmUWxtbmhOOGozK1d6VmJQUU55MVdzUjdpOWU5ajYzRXFLdDIyUTlPWEwrV0FjS3NrCm9JU21DTlZSVUFqVThZUlZjZ1FKQit6UTM0QVFQbHowT3A1Ty9RTi9NZWRqYUY4d0xTK2l2L3p2aVM4Y3FQYngKbzZzTHE2Rk5UbHRrL1FreGVDZUtLVFFlLzNrUFl2UUFkbmw2NVFJREFRQUJBb0lCQVFEQVQ0eXN2V2pSY3pxcgpKcU9SeGFPQTJEY3dXazJML1JXOFhtQWhaRmRTWHV2MkNQbGxhTU1yelBmTG41WUlmaHQzSDNzODZnSEdZc3pnClo4aWJiYWtYNUdFQ0t5N3lRSDZuZ3hFS3pRVGpiampBNWR3S0h0UFhQUnJmamQ1Y2FMczVpcDcxaWxCWEYxU3IKWERIaXUycnFtaC9kVTArWGRMLzNmK2VnVDl6bFQ5YzRyUm84dnZueWNYejFyMnVhRVZ2VExsWHVsb2NpeEVrcgoySjlTMmxveWFUb2tFTnNlMDNpSVdaWnpNNElZcVowOGJOeG9IWCszQXVlWExIUStzRkRKMlhaVVdLSkZHMHUyClp3R2w3YlZpRTFQNXdiQUdtZzJDeDVCN1MrdGQyUEpSV3Frb2VxY3F2RVdCc3RFL1FEcDFpVThCOHpiQXd0Y3IKZHc5TXZ6Q2hBb0dCQVBObzRWMjF6MGp6MWdEb2tlTVN5d3JnL2E4RkJSM2R2Y0xZbWV5VXkybmd3eHVucnFsdwo2U2IrOWdrOGovcXEvc3VQSDhVdzNqSHNKYXdGSnNvTkVqNCt2b1ZSM3UrbE5sTEw5b21rMXBoU0dNdVp0b3huCm5nbUxVbkJUMGI1M3BURkJ5WGsveE5CbElreWdBNlg5T2MreW5na3RqNlRyVnMxUERTdnVJY0s1QW9HQkFQZmoKcEUzR2F6cVFSemx6TjRvTHZmQWJBdktCZ1lPaFNnemxsK0ZLZkhzYWJGNkdudFd1dWVhY1FIWFpYZTA1c2tLcApXN2xYQ3dqQU1iUXI3QmdlazcrOSszZElwL1RnYmZCYnN3Syt6Vng3Z2doeWMrdytXRWExaHByWTZ6YXdxdkFaCkhRU2lMUEd1UGp5WXBQa1E2ZFdEczNmWHJGZ1dlTmd4SkhTZkdaT05Bb0dCQUt5WTF3MUM2U3Y2c3VuTC8vNTcKQ2Z5NTAwaXlqNUZBOWRqZkRDNWt4K1JZMnlDV0ExVGsybjZyVmJ6dzg4czBTeDMrYS9IQW1CM2dMRXBSRU5NKwo5NHVwcENFWEQ3VHdlcGUxUnlrTStKbmp4TzlDSE41c2J2U25sUnBQWlMvZzJRTVhlZ3grK2trbkhXNG1ITkFyCndqMlRrMXBBczFXbkJ0TG9WaGVyY01jSkFvR0JBSTYwSGdJb0Y5SysvRUcyY21LbUg5SDV1dGlnZFU2eHEwK0IKWE0zMWMzUHE0amdJaDZlN3pvbFRxa2d0dWtTMjBraE45dC9ibkI2TmhnK1N1WGVwSXFWZldVUnlMejVwZE9ESgo2V1BMTTYzcDdCR3cwY3RPbU1NYi9VRm5Yd0U4OHlzRlNnOUF6VjdVVUQvU0lDYkI5ZHRVMWh4SHJJK0pZRWdWCkFrZWd6N2lCQW9HQkFJRncrQVFJZUIwM01UL0lCbGswNENQTDJEak0rNDhoVGRRdjgwMDBIQU9mUWJrMEVZUDEKQ2FLR3RDbTg2MXpBZjBzcS81REtZQ0l6OS9HUzNYRk00Qm1rRk9nY1NXVENPNmZmTGdLM3FmQzN4WDJudlpIOQpYZGNKTDQrZndhY0x4c2JJKzhhUWNOVHRtb3pkUjEzQnNmUmIrSGpUL2o3dkdrYlFnSkhCT0syegotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=",
|
||||
jose.RS256,
|
||||
`{"use":"sig","kty":"RSA","kid":"f0cc8033b422c2a199dcb456dde29589a9f5edd27d1c345bdf308957e957becf","alg":"RS256","n":"67KjqmQYGq0MVtACVpeCmXminlQbDPGLmsZAUEwueHQnrt3WtvpDOm6AlaJMUnW-Hu55jjokalKeVjTKmgYGbqUzVDoMbPDaHekltdBTMGlOUFsP4UJSDrO4zdN-zo428TX2PnG2FCdVKGy4PE8ilHbWLcr871YjV51fw8CLDX9PZJNu861CF7V9iEJm6sSfQlmnhN8j3-WzVbPQNy1WsR7i9e9j63EqKt22Q9OXL-WAcKskoISmCNVRUAjU8YRVcgQJB-zQ34AQPlz0Op5O_QN_MedjaF8wLS-iv_zviS8cqPbxo6sLq6FNTltk_QkxeCeKKTQe_3kPYvQAdnl65Q","e":"AQAB","d":"wE-MrL1o0XM6qyajkcWjgNg3MFpNi_0VvF5gIWRXUl7r9gj5ZWjDK8z3y5-WCH4bdx97POoBxmLM4GfIm22pF-RhAisu8kB-p4MRCs0E4244wOXcCh7T1z0a343eXGi7OYqe9YpQVxdUq1wx4rtq6pof3VNPl3S_93_noE_c5U_XOK0aPL758nF89a9rmhFb0y5V7paHIsRJK9ifUtpaMmk6JBDbHtN4iFmWczOCGKmdPGzcaB1_twLnlyx0PrBQydl2VFiiRRtLtmcBpe21YhNT-cGwBpoNgseQe0vrXdjyUVqpKHqnKrxFgbLRP0A6dYlPAfM2wMLXK3cPTL8woQ","p":"82jhXbXPSPPWAOiR4xLLCuD9rwUFHd29wtiZ7JTLaeDDG6euqXDpJv72CTyP-qr-y48fxTDeMewlrAUmyg0SPj6-hVHe76U2Usv2iaTWmFIYy5m2jGeeCYtScFPRvnelMUHJeT_E0GUiTKADpf05z7KeCS2PpOtWzU8NK-4hwrk","q":"9-OkTcZrOpBHOXM3igu98BsC8oGBg6FKDOWX4Up8expsXoae1a655pxAddld7TmyQqlbuVcLCMAxtCvsGB6Tv737d0in9OBt8FuzAr7NXHuCCHJz7D5YRrWGmtjrNrCq8BkdBKIs8a4-PJik-RDp1YOzd9esWBZ42DEkdJ8Zk40","dp":"rJjXDULpK_qy6cv__nsJ_LnTSLKPkUD12N8MLmTH5FjbIJYDVOTafqtVvPDzyzRLHf5r8cCYHeAsSlEQ0z73i6mkIRcPtPB6l7VHKQz4mePE70Ic3mxu9KeVGk9lL-DZAxd6DH76SScdbiYc0CvCPZOTWkCzVacG0uhWF6twxwk","dq":"jrQeAigX0r78QbZyYqYf0fm62KB1TrGrT4FczfVzc-riOAiHp7vOiVOqSC26RLbSSE3239ucHo2GD5K5d6kipV9ZRHIvPml04MnpY8szrensEbDRy06Ywxv9QWdfATzzKwVKD0DNXtRQP9IgJsH121TWHEesj4lgSBUCR6DPuIE","qi":"gXD4BAh4HTcxP8gGWTTgI8vYOMz7jyFN1C_zTTQcA59BuTQRg_UJooa0KbzrXMB_Syr_kMpgIjP38ZLdcUzgGaQU6BxJZMI7p98uArep8LfFfae9kf1d1wkvj5_BpwvGxsj7xpBw1O2ajN1HXcGx9Fv4eNP-Pu8aRtCAkcE4rbM"}`,
|
||||
false,
|
||||
},
|
||||
{"good SS256",
|
||||
"LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUJlMFRxbXJkSXBZWE03c3pSRERWYndXOS83RWJHVWhTdFFJalhsVHNXM1BvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFb0xaRDI2bEdYREhRQmhhZkdlbEVmRDdlNmYzaURjWVJPVjdUbFlIdHF1Y1BFL2hId2dmYQpNY3FBUEZsRmpueUpySXJhYTFlQ2xZRTJ6UktTQk5kNXBRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=",
|
||||
jose.ES256,
|
||||
`{"use":"sig","kty":"EC","kid":"d591aa6e01e57ea8b80f349dc5de8517aa7b1f12f77700d89cbdba83938c0c61","crv":"P-256","alg":"ES256","x":"oLZD26lGXDHQBhafGelEfD7e6f3iDcYROV7TlYHtquc","y":"DxP4R8IH2jHKgDxZRY58iayK2mtXgpWBNs0SkgTXeaU","d":"F7ROqat0ilhczuzNEMNVvBb3_sRsZSFK1AiNeVOxbc8"}`,
|
||||
false,
|
||||
},
|
||||
{"unknown signing key type",
|
||||
"LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUJlMFRxbXJkSXBZWE03c3pSRERWYndXOS83RWJHVWhTdFFJalhsVHNXM1BvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFb0xaRDI2bEdYREhRQmhhZkdlbEVmRDdlNmYzaURjWVJPVjdUbFlIdHF1Y1BFL2hId2dmYQpNY3FBUEZsRmpueUpySXJhYTFlQ2xZRTJ6UktTQk5kNXBRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=",
|
||||
jose.HS256,
|
||||
`null`,
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
data, err := base64.StdEncoding.DecodeString(tt.cert)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
out, err := PrivateJWKFromBytes(data, tt.alg)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("PrivateJWKFromBytes() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
got, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diff := cmp.Diff(string(got), tt.want); diff != "" {
|
||||
t.Errorf("PrivateJWKFromBytes() want %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicJWKFromBytes(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
cert string
|
||||
alg jose.SignatureAlgorithm
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{"good RS256",
|
||||
"LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcGdJQkFBS0NBUUVBNjdLanFtUVlHcTBNVnRBQ1ZwZUNtWG1pbmxRYkRQR0xtc1pBVUV3dWVIUW5ydDNXCnR2cERPbTZBbGFKTVVuVytIdTU1ampva2FsS2VWalRLbWdZR2JxVXpWRG9NYlBEYUhla2x0ZEJUTUdsT1VGc1AKNFVKU0RyTzR6ZE4rem80MjhUWDJQbkcyRkNkVktHeTRQRThpbEhiV0xjcjg3MVlqVjUxZnc4Q0xEWDlQWkpOdQo4NjFDRjdWOWlFSm02c1NmUWxtbmhOOGozK1d6VmJQUU55MVdzUjdpOWU5ajYzRXFLdDIyUTlPWEwrV0FjS3NrCm9JU21DTlZSVUFqVThZUlZjZ1FKQit6UTM0QVFQbHowT3A1Ty9RTi9NZWRqYUY4d0xTK2l2L3p2aVM4Y3FQYngKbzZzTHE2Rk5UbHRrL1FreGVDZUtLVFFlLzNrUFl2UUFkbmw2NVFJREFRQUJBb0lCQVFEQVQ0eXN2V2pSY3pxcgpKcU9SeGFPQTJEY3dXazJML1JXOFhtQWhaRmRTWHV2MkNQbGxhTU1yelBmTG41WUlmaHQzSDNzODZnSEdZc3pnClo4aWJiYWtYNUdFQ0t5N3lRSDZuZ3hFS3pRVGpiampBNWR3S0h0UFhQUnJmamQ1Y2FMczVpcDcxaWxCWEYxU3IKWERIaXUycnFtaC9kVTArWGRMLzNmK2VnVDl6bFQ5YzRyUm84dnZueWNYejFyMnVhRVZ2VExsWHVsb2NpeEVrcgoySjlTMmxveWFUb2tFTnNlMDNpSVdaWnpNNElZcVowOGJOeG9IWCszQXVlWExIUStzRkRKMlhaVVdLSkZHMHUyClp3R2w3YlZpRTFQNXdiQUdtZzJDeDVCN1MrdGQyUEpSV3Frb2VxY3F2RVdCc3RFL1FEcDFpVThCOHpiQXd0Y3IKZHc5TXZ6Q2hBb0dCQVBObzRWMjF6MGp6MWdEb2tlTVN5d3JnL2E4RkJSM2R2Y0xZbWV5VXkybmd3eHVucnFsdwo2U2IrOWdrOGovcXEvc3VQSDhVdzNqSHNKYXdGSnNvTkVqNCt2b1ZSM3UrbE5sTEw5b21rMXBoU0dNdVp0b3huCm5nbUxVbkJUMGI1M3BURkJ5WGsveE5CbElreWdBNlg5T2MreW5na3RqNlRyVnMxUERTdnVJY0s1QW9HQkFQZmoKcEUzR2F6cVFSemx6TjRvTHZmQWJBdktCZ1lPaFNnemxsK0ZLZkhzYWJGNkdudFd1dWVhY1FIWFpYZTA1c2tLcApXN2xYQ3dqQU1iUXI3QmdlazcrOSszZElwL1RnYmZCYnN3Syt6Vng3Z2doeWMrdytXRWExaHByWTZ6YXdxdkFaCkhRU2lMUEd1UGp5WXBQa1E2ZFdEczNmWHJGZ1dlTmd4SkhTZkdaT05Bb0dCQUt5WTF3MUM2U3Y2c3VuTC8vNTcKQ2Z5NTAwaXlqNUZBOWRqZkRDNWt4K1JZMnlDV0ExVGsybjZyVmJ6dzg4czBTeDMrYS9IQW1CM2dMRXBSRU5NKwo5NHVwcENFWEQ3VHdlcGUxUnlrTStKbmp4TzlDSE41c2J2U25sUnBQWlMvZzJRTVhlZ3grK2trbkhXNG1ITkFyCndqMlRrMXBBczFXbkJ0TG9WaGVyY01jSkFvR0JBSTYwSGdJb0Y5SysvRUcyY21LbUg5SDV1dGlnZFU2eHEwK0IKWE0zMWMzUHE0amdJaDZlN3pvbFRxa2d0dWtTMjBraE45dC9ibkI2TmhnK1N1WGVwSXFWZldVUnlMejVwZE9ESgo2V1BMTTYzcDdCR3cwY3RPbU1NYi9VRm5Yd0U4OHlzRlNnOUF6VjdVVUQvU0lDYkI5ZHRVMWh4SHJJK0pZRWdWCkFrZWd6N2lCQW9HQkFJRncrQVFJZUIwM01UL0lCbGswNENQTDJEak0rNDhoVGRRdjgwMDBIQU9mUWJrMEVZUDEKQ2FLR3RDbTg2MXpBZjBzcS81REtZQ0l6OS9HUzNYRk00Qm1rRk9nY1NXVENPNmZmTGdLM3FmQzN4WDJudlpIOQpYZGNKTDQrZndhY0x4c2JJKzhhUWNOVHRtb3pkUjEzQnNmUmIrSGpUL2o3dkdrYlFnSkhCT0syegotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=",
|
||||
jose.RS256,
|
||||
`{"use":"sig","kty":"RSA","kid":"f0cc8033b422c2a199dcb456dde29589a9f5edd27d1c345bdf308957e957becf","alg":"RS256","n":"67KjqmQYGq0MVtACVpeCmXminlQbDPGLmsZAUEwueHQnrt3WtvpDOm6AlaJMUnW-Hu55jjokalKeVjTKmgYGbqUzVDoMbPDaHekltdBTMGlOUFsP4UJSDrO4zdN-zo428TX2PnG2FCdVKGy4PE8ilHbWLcr871YjV51fw8CLDX9PZJNu861CF7V9iEJm6sSfQlmnhN8j3-WzVbPQNy1WsR7i9e9j63EqKt22Q9OXL-WAcKskoISmCNVRUAjU8YRVcgQJB-zQ34AQPlz0Op5O_QN_MedjaF8wLS-iv_zviS8cqPbxo6sLq6FNTltk_QkxeCeKKTQe_3kPYvQAdnl65Q","e":"AQAB"}`,
|
||||
false,
|
||||
},
|
||||
|
||||
{"good ES256",
|
||||
"LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUJlMFRxbXJkSXBZWE03c3pSRERWYndXOS83RWJHVWhTdFFJalhsVHNXM1BvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFb0xaRDI2bEdYREhRQmhhZkdlbEVmRDdlNmYzaURjWVJPVjdUbFlIdHF1Y1BFL2hId2dmYQpNY3FBUEZsRmpueUpySXJhYTFlQ2xZRTJ6UktTQk5kNXBRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=",
|
||||
jose.ES256,
|
||||
`{"use":"sig","kty":"EC","kid":"d591aa6e01e57ea8b80f349dc5de8517aa7b1f12f77700d89cbdba83938c0c61","crv":"P-256","alg":"ES256","x":"oLZD26lGXDHQBhafGelEfD7e6f3iDcYROV7TlYHtquc","y":"DxP4R8IH2jHKgDxZRY58iayK2mtXgpWBNs0SkgTXeaU"}`,
|
||||
false,
|
||||
},
|
||||
{"unknown signing key type",
|
||||
"LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUJlMFRxbXJkSXBZWE03c3pSRERWYndXOS83RWJHVWhTdFFJalhsVHNXM1BvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFb0xaRDI2bEdYREhRQmhhZkdlbEVmRDdlNmYzaURjWVJPVjdUbFlIdHF1Y1BFL2hId2dmYQpNY3FBUEZsRmpueUpySXJhYTFlQ2xZRTJ6UktTQk5kNXBRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=",
|
||||
jose.HS256,
|
||||
`null`,
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
data, err := base64.StdEncoding.DecodeString(tt.cert)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
out, err := PublicJWKFromBytes(data, tt.alg)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("PublicJWKFromBytes() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
got, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diff := cmp.Diff(string(got), tt.want); diff != "" {
|
||||
t.Errorf("PublicJWKFromBytes() want %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
package cryptutil
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// NewSigningKey generates a random P-256 ECDSA private key.
|
||||
// Go's P-256 is constant-time (which prevents certain types of attacks)
|
||||
// while its P-384 and P-521 are not.
|
||||
func NewSigningKey() (*ecdsa.PrivateKey, error) {
|
||||
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
}
|
||||
|
||||
// Sign signs arbitrary data using ECDSA.
|
||||
func Sign(data []byte, privkey *ecdsa.PrivateKey) ([]byte, error) {
|
||||
// hash message
|
||||
digest := sha256.Sum256(data)
|
||||
|
||||
// sign the hash
|
||||
r, s, err := ecdsa.Sign(rand.Reader, privkey, digest[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// encode the signature {R, S}
|
||||
// big.Int.Bytes() will need padding in the case of leading zero bytes
|
||||
params := privkey.Curve.Params()
|
||||
curveOrderByteSize := params.P.BitLen() / 8
|
||||
rBytes, sBytes := r.Bytes(), s.Bytes()
|
||||
signature := make([]byte, curveOrderByteSize*2)
|
||||
copy(signature[curveOrderByteSize-len(rBytes):], rBytes)
|
||||
copy(signature[curveOrderByteSize*2-len(sBytes):], sBytes)
|
||||
return signature, nil
|
||||
}
|
||||
|
||||
// Verify checks a raw ECDSA signature.
|
||||
// Returns true if it's valid and false if not.
|
||||
func Verify(data, signature []byte, pubkey *ecdsa.PublicKey) bool {
|
||||
// hash message
|
||||
digest := sha256.Sum256(data)
|
||||
curveOrderByteSize := pubkey.Curve.Params().P.BitLen() / 8
|
||||
r, s := new(big.Int), new(big.Int)
|
||||
r.SetBytes(signature[:curveOrderByteSize])
|
||||
s.SetBytes(signature[curveOrderByteSize:])
|
||||
return ecdsa.Verify(pubkey, digest[:], r, s)
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
package cryptutil
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSign(t *testing.T) {
|
||||
message := []byte("Hello, world!")
|
||||
|
||||
key, err := NewSigningKey()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
signature, err := Sign(message, key)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !Verify(message, signature, &key.PublicKey) {
|
||||
t.Error("signature was not correct")
|
||||
return
|
||||
}
|
||||
|
||||
message[0] ^= 0xff
|
||||
if Verify(message, signature, &key.PublicKey) {
|
||||
t.Error("signature was good for altered message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignWithP384(t *testing.T) {
|
||||
message := []byte("Hello, world!")
|
||||
|
||||
key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
signature, err := Sign(message, key)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !Verify(message, signature, &key.PublicKey) {
|
||||
t.Error("signature was not correct")
|
||||
return
|
||||
}
|
||||
|
||||
message[0] ^= 0xff
|
||||
if Verify(message, signature, &key.PublicKey) {
|
||||
t.Error("signature was good for altered message")
|
||||
}
|
||||
}
|
29
internal/cryptutil/testdata/ca.pem
vendored
29
internal/cryptutil/testdata/ca.pem
vendored
|
@ -1,29 +0,0 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIE5DCCAsygAwIBAgIBATANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDEwdnb29k
|
||||
LWNhMB4XDTE5MDgxMDE3NDAwMloXDTIxMDIxMDE3NDAwMlowEjEQMA4GA1UEAxMH
|
||||
Z29vZC1jYTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL7oeetJ/6cE
|
||||
Gbq7/jsmqOE3erTMZDttxS8I5FWW4NDWmci8NHudLd8e3Rm8HzcOcJ4P/A+p5lbk
|
||||
N8rK689IlC8j3o2hHRtI6OmlhV74J1iIH8kdIu6WlO1kNuLytdkn8Qk+I9CDZ9FH
|
||||
+g8QnudkKLQbddWCUrsR4xpG2+Ed5gnkBI4n3ncK0X/XFhqhCLE5xPZBNNZKFlrq
|
||||
meaIxtz2sfoYV56g02sFKT1IIL55E0mxv4TkbmIl9FOjdKBvHEfrGyyy8TFLq+S3
|
||||
Mz6zoq48n8HF1G9ppKVON1Jt2K5Qa/Whin5kqcaa3p4M/7kb6kqSKLXmb7H27y/U
|
||||
Db6CPm5wihv06sQhmsv0xnKhj2o/C8eqls76YX1uccj39fI4eCQ8pCEm9Up8yfI/
|
||||
LeaZWla44Fgyl77Yrsc/3E9vMaKFUxdcGum1t+5FYajdcA/NQky2Ay2jptpUuuHU
|
||||
6xXK7Dqv9gMcA/33UX8ZGfIOFM+cqe941i5OOXFHrhD9jy4PGc8gi1I4r+UWwKBb
|
||||
1Hh5rT7rBY+SKM0sfkiBVuEOiny6t1ugkDv68usEXYHZVWiizoXfp5Gn6frE/wR1
|
||||
DdjMwLa/OkPNuDUT8yMFKhVFqGqwGC66o+5r422UpkK8Hry+klCzTO+7SDhu2bZN
|
||||
QQFOCKIUevtwlgZmPMrMA56ww5RJsaVxAgMBAAGjRTBDMA4GA1UdDwEB/wQEAwIB
|
||||
BjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBR54JCzLFX4ODSCRtucAPfN
|
||||
uXVznDANBgkqhkiG9w0BAQsFAAOCAgEAf+nRjAVvnOJRrJAAjJYV7iQwlq1QvXDg
|
||||
lvacBhTRrXXx9nFiTYS5x2ALmvyXxnm7HKeCIFDrRp8NLTY2b2WGMAq1qsrAOD/z
|
||||
S6cRImNCmP6gtPsT49Zo0Xj3kf6rMpOxpbIIgJfLcNzPfi/ncx/hD3A8yzfN0A6Y
|
||||
qEwd/JFOj7DktZBgeIvDNRWKJoxJIFVxjrj/1bVi1e4VV5oZhNjJ8K9rWQQ+Q/+t
|
||||
gyF+K2plCCTbEdzyOax65v8yP2yD+6ZAHFOlF26NviRL89buTr0hJYkCyUvw2bYi
|
||||
8Cs2X6dwCCuuaeGUGeQzk30lPyGVJeJ/vI0bQK9igb9tZ3cwtXpPv3zkPuL17wMV
|
||||
+B1z6EmGeUK5ySCLEZ76iYb5M/cvcMEN1ghxSH7Afh8LKG4yk3OmRCnwjEju1aij
|
||||
dk7r2nsLfaOJXPQ5Mp34XSYmu9iMYtW+LmfbH2q1ooKwJd8G5XaEdfBjGPD9CqdX
|
||||
aJXt004qWljRNKzu0SEFbzRWF4thyyTO18AR8y3GWEpjOyjgJK9EyMlBoPktXAUU
|
||||
6sLXjOvYSJ/wm65HqUVAM5lG/zYSwLgBL00sZI+5FkD0nU4FLzAdKWNKZDWdUMmL
|
||||
V/eWFF4l0TPo55a3JT/PFsbtDPKUloYQWy1rnajGru/F9ldBppumETkaNKfVONIr
|
||||
xpDgsQaVEW8=
|
||||
-----END CERTIFICATE-----
|
11
internal/cryptutil/testdata/example-cert.pem
vendored
11
internal/cryptutil/testdata/example-cert.pem
vendored
|
@ -1,11 +0,0 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw
|
||||
DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow
|
||||
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d
|
||||
7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B
|
||||
5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr
|
||||
BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1
|
||||
NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l
|
||||
Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc
|
||||
6MF9+Yw1Yy0t
|
||||
-----END CERTIFICATE-----
|
5
internal/cryptutil/testdata/example-key.pem
vendored
5
internal/cryptutil/testdata/example-key.pem
vendored
|
@ -1,5 +0,0 @@
|
|||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49
|
||||
AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q
|
||||
EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==
|
||||
-----END EC PRIVATE KEY-----
|
BIN
internal/cryptutil/testdata/random
vendored
BIN
internal/cryptutil/testdata/random
vendored
Binary file not shown.
|
@ -1,51 +0,0 @@
|
|||
package cryptutil
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
)
|
||||
|
||||
// GetCertificateForDomain returns the tls Certificate which matches the given domain name.
|
||||
// It should handle both exact matches and wildcard matches. If none of those match, the first certificate will be used.
|
||||
// Finally if there are no matching certificates one will be generated.
|
||||
func GetCertificateForDomain(certificates []tls.Certificate, domain string) (*tls.Certificate, error) {
|
||||
// first try a direct name match
|
||||
for _, cert := range certificates {
|
||||
if matchesDomain(&cert, domain) {
|
||||
return &cert, nil
|
||||
}
|
||||
}
|
||||
|
||||
// next use the first cert
|
||||
if len(certificates) > 0 {
|
||||
return &certificates[0], nil
|
||||
}
|
||||
|
||||
// finally fall back to a generated, self-signed certificate
|
||||
return GenerateSelfSignedCertificate(domain)
|
||||
}
|
||||
|
||||
func matchesDomain(cert *tls.Certificate, domain string) bool {
|
||||
if cert == nil || len(cert.Certificate) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
xcert, err := x509.ParseCertificate(cert.Certificate[0])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if certmagic.MatchWildcard(domain, xcert.Subject.CommonName) {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, san := range xcert.DNSNames {
|
||||
if certmagic.MatchWildcard(domain, san) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
package cryptutil
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetCertificateForDomain(t *testing.T) {
|
||||
gen := func(t *testing.T, domain string) *tls.Certificate {
|
||||
cert, err := GenerateSelfSignedCertificate(domain)
|
||||
if !assert.NoError(t, err, "error generating certificate for: %s", domain) {
|
||||
t.FailNow()
|
||||
}
|
||||
return cert
|
||||
}
|
||||
|
||||
t.Run("exact match", func(t *testing.T) {
|
||||
certs := []tls.Certificate{
|
||||
*gen(t, "a.example.com"),
|
||||
*gen(t, "b.example.com"),
|
||||
}
|
||||
|
||||
found, err := GetCertificateForDomain(certs, "b.example.com")
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, &certs[1], found)
|
||||
})
|
||||
t.Run("wildcard match", func(t *testing.T) {
|
||||
certs := []tls.Certificate{
|
||||
*gen(t, "a.example.com"),
|
||||
*gen(t, "*.example.com"),
|
||||
}
|
||||
|
||||
found, err := GetCertificateForDomain(certs, "b.example.com")
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, &certs[1], found)
|
||||
})
|
||||
t.Run("no name match", func(t *testing.T) {
|
||||
certs := []tls.Certificate{
|
||||
*gen(t, "a.example.com"),
|
||||
}
|
||||
|
||||
found, err := GetCertificateForDomain(certs, "b.example.com")
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, &certs[0], found)
|
||||
})
|
||||
t.Run("generate", func(t *testing.T) {
|
||||
certs := []tls.Certificate{}
|
||||
|
||||
found, err := GetCertificateForDomain(certs, "b.example.com")
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
assert.NotNil(t, found)
|
||||
})
|
||||
}
|
|
@ -10,8 +10,8 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
"github.com/pomerium/pomerium/pkg/cryptutil"
|
||||
)
|
||||
|
||||
// EncryptedCompressedJSON implements SecureEncoder for JSON using an AEAD cipher.
|
||||
|
|
|
@ -9,11 +9,11 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
"github.com/pomerium/pomerium/internal/encoding/ecjson"
|
||||
"github.com/pomerium/pomerium/internal/encoding/mock"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
"github.com/pomerium/pomerium/pkg/cryptutil"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
|
|
|
@ -13,8 +13,8 @@ import (
|
|||
"github.com/google/go-cmp/cmp"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/internal/encoding/ecjson"
|
||||
"github.com/pomerium/pomerium/pkg/cryptutil"
|
||||
)
|
||||
|
||||
func testAuthorizer(next http.Handler) http.Handler {
|
||||
|
|
|
@ -8,9 +8,9 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/internal/encoding/ecjson"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
"github.com/pomerium/pomerium/pkg/cryptutil"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
|
|
|
@ -12,10 +12,10 @@ import (
|
|||
"github.com/google/go-cmp/cmp"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/internal/encoding/jws"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
"github.com/pomerium/pomerium/internal/sessions/mock"
|
||||
"github.com/pomerium/pomerium/pkg/cryptutil"
|
||||
)
|
||||
|
||||
func TestNewContext(t *testing.T) {
|
||||
|
|
|
@ -8,9 +8,9 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/internal/encoding/ecjson"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
"github.com/pomerium/pomerium/pkg/cryptutil"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/pkg/cryptutil"
|
||||
)
|
||||
|
||||
// SignedURL is a shared-key HMAC wrapped URL.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue