Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add X.509 certificate parsing #1014

Merged
merged 63 commits into from
May 17, 2019
Merged
Show file tree
Hide file tree
Changes from 58 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
924987e
x509: Parse signature algorithm
bookmoons May 2, 2019
8658192
x509: Stringify unknown signature algorithm
bookmoons May 2, 2019
b8b29b4
x509: Parse certificate subject
bookmoons May 3, 2019
f5aeaaf
x509: Parse postal code
bookmoons May 3, 2019
26ae498
x509: Parse province
bookmoons May 3, 2019
817aa16
x509: Parse locality
bookmoons May 3, 2019
486989f
x509: Parse street address
bookmoons May 3, 2019
9729576
x509: Parse organization
bookmoons May 3, 2019
1a55042
x509: Parse organizational units
bookmoons May 3, 2019
62c678e
x509: Parse subject common name
bookmoons May 3, 2019
6d507e7
x509: Qualify subject error messages
bookmoons May 3, 2019
12ef550
x509: Parse issuer country
bookmoons May 3, 2019
66ebce6
x509: Parse issuer province
bookmoons May 3, 2019
5a5c09f
x509: Parse issuer locality
bookmoons May 3, 2019
ccbad81
x509: Parse issuer organization
bookmoons May 3, 2019
8c6badf
Add missing semicolon
bookmoons May 3, 2019
0f2a328
x509: Parse issuer common name
bookmoons May 3, 2019
794f219
x509: Parse lower bound
bookmoons May 3, 2019
686d5fe
x509: Parse upper bound
bookmoons May 3, 2019
f989961
x509: Parse alternate names
bookmoons May 3, 2019
7c0f298
x509: Parse fingerprint
bookmoons May 3, 2019
f2f5452
x509: Rename countryName country
bookmoons May 3, 2019
e9a09cd
x509: Remove subtest name redundancy
bookmoons May 3, 2019
d3b5f41
x509: Parse public key algorithm
bookmoons May 3, 2019
f69cd25
x509: Skip on short test
bookmoons May 3, 2019
26775d6
x509: Parse public key exponent
bookmoons May 3, 2019
288e1e4
x509: Parse public key modulus
bookmoons May 3, 2019
59486b3
x509: Factor test material
bookmoons May 3, 2019
a05c1d1
x509: Add getIssuer
bookmoons May 3, 2019
ab3c4de
x509: Add getSubject
bookmoons May 3, 2019
316b459
x509: Add getAltNames
bookmoons May 3, 2019
6833737
Uncapitalize error strings
bookmoons May 3, 2019
1f806dd
Wipe up lint
bookmoons May 3, 2019
189daa5
Make internal functions unexported
bookmoons May 3, 2019
67f9b47
Wipe up lint
bookmoons May 3, 2019
ffd9edf
Correct formatting
bookmoons May 3, 2019
305b6ac
x509: Accept SHA-1 use
bookmoons May 7, 2019
7e4f5bf
x509: Correct comment
bookmoons May 9, 2019
c1f7d53
x509: Factor JavaScript error throwing
bookmoons May 9, 2019
11d4538
x509: Isolate JavaScript error throwing
bookmoons May 9, 2019
cfb9d7a
x509: Use error wrapping
bookmoons May 9, 2019
26ab8df
x509: Check error message
bookmoons May 9, 2019
4eedcfc
x509: Lowercase utility functions
bookmoons May 9, 2019
446734c
x509: Add test material structure
bookmoons May 9, 2019
248e424
x509: Test parse failure of valid PEM
bookmoons May 9, 2019
2802dc4
x509: Add subject names
bookmoons May 10, 2019
7af94e1
x509: Add issuer names
bookmoons May 10, 2019
4d8098c
x509: Detect names in shortcut methods
bookmoons May 10, 2019
5006ab9
x509: Move public key algorithm to certificate root
bookmoons May 10, 2019
12d89a1
x509: Use combined public key in certificate
bookmoons May 10, 2019
4e0c899
x509: Add DSA public key parsing
bookmoons May 10, 2019
84e10db
x509: Add ECDSA public key parsing
bookmoons May 10, 2019
1d55323
Format code
bookmoons May 10, 2019
14806cb
x509: Minimize type assertions
bookmoons May 10, 2019
dac9da9
Correct formatting
bookmoons May 15, 2019
cb3fabe
x509: Move algorithm into public key
bookmoons May 15, 2019
3a13e19
x509: Abstract public key
bookmoons May 15, 2019
67b97b8
x509: Allow test material global
bookmoons May 15, 2019
40d513f
x509: Accept encoded certificate as []byte
bookmoons May 16, 2019
f2bbb77
x509: Test signature algorithm
bookmoons May 16, 2019
793ee98
x509: Test make public key
bookmoons May 16, 2019
99e08b7
x509: Test make certificate
bookmoons May 16, 2019
44cfa25
x509: Correct make certificate return
bookmoons May 16, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions js/modules/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ package modules
import (
"github.com/loadimpact/k6/js/modules/k6"
"github.com/loadimpact/k6/js/modules/k6/crypto"
"github.com/loadimpact/k6/js/modules/k6/crypto/x509"
"github.com/loadimpact/k6/js/modules/k6/encoding"
"github.com/loadimpact/k6/js/modules/k6/html"
"github.com/loadimpact/k6/js/modules/k6/http"
Expand All @@ -32,11 +33,12 @@ import (

// Index of module implementations.
var Index = map[string]interface{}{
"k6": k6.New(),
"k6/crypto": crypto.New(),
"k6/encoding": encoding.New(),
"k6/http": http.New(),
"k6/metrics": metrics.New(),
"k6/html": html.New(),
"k6/ws": ws.New(),
"k6": k6.New(),
"k6/crypto": crypto.New(),
"k6/crypto/x509": x509.New(),
"k6/encoding": encoding.New(),
"k6/http": http.New(),
"k6/metrics": metrics.New(),
"k6/html": html.New(),
"k6/ws": ws.New(),
}
274 changes: 274 additions & 0 deletions js/modules/k6/crypto/x509/x509.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
/*
*
* k6 - a next-generation load testing tool
* Copyright (C) 2019 Load Impact
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package x509

import (
"context"
"crypto/dsa"
"crypto/ecdsa"
"crypto/rsa"
"crypto/sha1" // #nosec G505
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"time"

"github.com/loadimpact/k6/js/common"
"github.com/pkg/errors"
)

// X509 certificate functionality
type X509 struct{}
bookmoons marked this conversation as resolved.
Show resolved Hide resolved

// Certificate is an X.509 certificate
type Certificate struct {
bookmoons marked this conversation as resolved.
Show resolved Hide resolved
Subject Subject
Issuer Issuer
NotBefore string `js:"notBefore"`
NotAfter string `js:"notAfter"`
AltNames []string `js:"altNames"`
SignatureAlgorithm string `js:"signatureAlgorithm"`
FingerPrint []byte `js:"fingerPrint"`
PublicKey PublicKey `js:"publicKey"`
}

// RDN is a component of an X.509 distinguished name
type RDN struct {
Type string
Value string
}

// Subject is a certificate subject
type Subject struct {
bookmoons marked this conversation as resolved.
Show resolved Hide resolved
CommonName string `js:"commonName"`
Country string
PostalCode string `js:"postalCode"`
StateOrProvinceName string `js:"stateOrProvinceName"`
LocalityName string `js:"localityName"`
StreetAddress string `js:"streetAddress"`
OrganizationName string `js:"organizationName"`
OrganizationalUnitName []string `js:"organizationalUnitName"`
Names []RDN
}

// Issuer is a certificate issuer
type Issuer struct {
bookmoons marked this conversation as resolved.
Show resolved Hide resolved
CommonName string `js:"commonName"`
Country string
StateOrProvinceName string `js:"stateOrProvinceName"`
LocalityName string `js:"localityName"`
OrganizationName string `js:"organizationName"`
Names []RDN
}

// PublicKey is used for decryption and signature verification
type PublicKey struct {
bookmoons marked this conversation as resolved.
Show resolved Hide resolved
Algorithm string
Key interface{}
}

// New constructs the X509 interface
func New() *X509 {
bookmoons marked this conversation as resolved.
Show resolved Hide resolved
return &X509{}
}

// Parse produces an entire X.509 certificate
func (X509) Parse(ctx context.Context, encoded string) Certificate {
bookmoons marked this conversation as resolved.
Show resolved Hide resolved
parsed, err := parseCertificate(encoded)
if err != nil {
throw(ctx, err)
}
certificate, err := makeCertificate(parsed)
if err != nil {
throw(ctx, err)
}
return certificate
}

// GetAltNames extracts alt names
func (X509) GetAltNames(ctx context.Context, encoded string) []string {
bookmoons marked this conversation as resolved.
Show resolved Hide resolved
parsed, err := parseCertificate(encoded)
if err != nil {
throw(ctx, err)
}
return altNames(parsed)
}

// GetIssuer extracts certificate issuer
func (X509) GetIssuer(ctx context.Context, encoded string) Issuer {
bookmoons marked this conversation as resolved.
Show resolved Hide resolved
parsed, err := parseCertificate(encoded)
if err != nil {
throw(ctx, err)
}
return makeIssuer(parsed.Issuer)
}

// GetSubject extracts certificate subject
func (X509) GetSubject(ctx context.Context, encoded string) Subject {
bookmoons marked this conversation as resolved.
Show resolved Hide resolved
parsed, err := parseCertificate(encoded)
if err != nil {
throw(ctx, err)
}
return makeSubject(parsed.Subject)
}

func parseCertificate(encoded string) (*x509.Certificate, error) {
decoded, _ := pem.Decode([]byte(encoded))
bookmoons marked this conversation as resolved.
Show resolved Hide resolved
if decoded == nil {
err := errors.New("failed to decode certificate PEM file")
return nil, err
}
parsed, err := x509.ParseCertificate(decoded.Bytes)
if err != nil {
err = errors.Wrap(err, "failed to parse certificate")
return nil, err
}
return parsed, nil
}

func makeCertificate(parsed *x509.Certificate) (Certificate, error) {
publicKey, err := makePublicKey(parsed.PublicKey)
if err != nil {
return Certificate{}, err
}
return Certificate{
Subject: makeSubject(parsed.Subject),
Issuer: makeIssuer(parsed.Issuer),
NotBefore: iso8601(parsed.NotBefore),
NotAfter: iso8601(parsed.NotAfter),
AltNames: altNames(parsed),
SignatureAlgorithm: signatureAlgorithm(parsed.SignatureAlgorithm),
FingerPrint: fingerPrint(parsed),
PublicKey: publicKey,
}, err
}

func makeSubject(subject pkix.Name) Subject {
return Subject{
CommonName: subject.CommonName,
Country: first(subject.Country),
PostalCode: first(subject.PostalCode),
StateOrProvinceName: first(subject.Province),
LocalityName: first(subject.Locality),
StreetAddress: first(subject.StreetAddress),
OrganizationName: first(subject.Organization),
OrganizationalUnitName: subject.OrganizationalUnit,
Names: makeRdns(subject.Names),
}
}

func makeIssuer(issuer pkix.Name) Issuer {
return Issuer{
CommonName: issuer.CommonName,
Country: first(issuer.Country),
StateOrProvinceName: first(issuer.Province),
LocalityName: first(issuer.Locality),
OrganizationName: first(issuer.Organization),
Names: makeRdns(issuer.Names),
}
}

func makePublicKey(parsed interface{}) (PublicKey, error) {
var algorithm string
switch parsed.(type) {
case *dsa.PublicKey:
algorithm = "DSA"
case *ecdsa.PublicKey:
algorithm = "ECDSA"
case *rsa.PublicKey:
algorithm = "RSA"
default:
err := errors.New("unsupported public key algorithm")
return PublicKey{}, err
}
return PublicKey{
Algorithm: algorithm,
Key: parsed,
}, nil
}

func first(values []string) string {
bookmoons marked this conversation as resolved.
Show resolved Hide resolved
if len(values) > 0 {
return values[0]
}
return ""
}

func iso8601(value time.Time) string {
return value.Format(time.RFC3339)
}

func makeRdns(names []pkix.AttributeTypeAndValue) []RDN {
var result = make([]RDN, len(names))
for i, name := range names {
result[i] = makeRdn(name)
}
return result
}

func makeRdn(name pkix.AttributeTypeAndValue) RDN {
return RDN{
Type: name.Type.String(),
Value: fmt.Sprintf("%v", name.Value),
}
}

func altNames(parsed *x509.Certificate) []string {
var names []string
names = append(names, parsed.DNSNames...)
names = append(names, parsed.EmailAddresses...)
names = append(names, ipAddresses(parsed)...)
names = append(names, uris(parsed)...)
return names
}

func ipAddresses(parsed *x509.Certificate) []string {
strings := make([]string, len(parsed.IPAddresses))
for i, item := range parsed.IPAddresses {
strings[i] = item.String()
}
return strings
}

func uris(parsed *x509.Certificate) []string {
strings := make([]string, len(parsed.URIs))
for i, item := range parsed.URIs {
strings[i] = item.String()
}
return strings
}

func signatureAlgorithm(value x509.SignatureAlgorithm) string {
if value == x509.UnknownSignatureAlgorithm {
return "UnknownSignatureAlgorithm"
}
return value.String()
}

func fingerPrint(parsed *x509.Certificate) []byte {
bytes := sha1.Sum(parsed.Raw) // #nosec G401
return bytes[:]
}

func throw(ctx context.Context, err error) {
common.Throw(common.GetRuntime(ctx), err)
}
Loading