Pulling out ca module

This commit is contained in:
Richard Barnes 2015-03-10 14:10:59 -07:00
parent c6673ade2e
commit 8791f6fc80
4 changed files with 157 additions and 43 deletions

View File

@ -7,13 +7,15 @@ package main
import (
"fmt"
"github.com/codegangsta/cli"
"github.com/letsencrypt/boulder"
"github.com/streadway/amqp"
"net/http"
"os"
"github.com/codegangsta/cli"
_ "github.com/mattn/go-sqlite3"
"github.com/streadway/amqp"
"github.com/letsencrypt/boulder"
"github.com/letsencrypt/boulder/ca"
)
// Exit and print error message if we encountered a problem
@ -108,7 +110,7 @@ func main() {
failOnError(err, "Unable to create SA")
ra := boulder.NewRegistrationAuthorityImpl()
va := boulder.NewValidationAuthorityImpl()
ca, err := boulder.NewCertificateAuthorityImpl(cfsslServer, authKey, profile)
ca, err := ca.NewCertificateAuthorityImpl(cfsslServer, authKey, profile)
failOnError(err, "Unable to create CA")
// Wire them up
@ -160,7 +162,7 @@ func main() {
// ... and corresponding servers
// (We need this order so that we can give the servers
// references to the clients)
cai, err := boulder.NewCertificateAuthorityImpl(cfsslServer, authKey, profile)
cai, err := ca.NewCertificateAuthorityImpl(cfsslServer, authKey, profile)
failOnError(err, "Failed to create CA impl")
cas, err := boulder.NewCertificateAuthorityServer("CA.server", ch, cai)
failOnError(err, "Failed to create CA server")
@ -243,7 +245,7 @@ func main() {
ch := amqpChannel(c.GlobalString("amqp"))
cai, err := boulder.NewCertificateAuthorityImpl(cfsslServer, authKey, profile)
cai, err := ca.NewCertificateAuthorityImpl(cfsslServer, authKey, profile)
failOnError(err, "Failed to create CA impl")
cas, err := boulder.NewCertificateAuthorityServer("CA.server", ch, cai)
failOnError(err, "Unable to create CA server")

View File

@ -3,7 +3,7 @@
// 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 boulder
package ca
import (
"crypto/x509"
@ -20,8 +20,8 @@ import (
)
type CertificateAuthorityImpl struct {
signer signer.Signer
profile string
Signer signer.Signer
SA core.StorageAuthority
}
@ -48,7 +48,7 @@ func NewCertificateAuthorityImpl(hostport string, authKey string, profile string
return
}
ca = &CertificateAuthorityImpl{signer: signer, profile: profile}
ca = &CertificateAuthorityImpl{Signer: signer, profile: profile}
return
}
@ -83,7 +83,7 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
Hosts: hostNames,
},
}
certPEM, err := ca.signer.Sign(req)
certPEM, err := ca.Signer.Sign(req)
if err != nil {
return
}

View File

@ -3,7 +3,7 @@
// 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 boulder
package ca
import (
"crypto/x509"
@ -11,9 +11,8 @@ import (
"encoding/pem"
"testing"
_ "github.com/mattn/go-sqlite3"
"github.com/cloudflare/cfssl/signer/local"
_ "github.com/mattn/go-sqlite3"
)
var CA_KEY_PEM = "-----BEGIN RSA PRIVATE KEY-----\n" +

View File

@ -16,6 +16,10 @@ import (
"github.com/bifurcation/gose"
"github.com/cloudflare/cfssl/signer/local"
_ "github.com/mattn/go-sqlite3"
"github.com/letsencrypt/boulder/ca"
"github.com/letsencrypt/boulder/core"
)
func TestForbiddenIdentifier(t *testing.T) {
@ -88,10 +92,10 @@ func TestForbiddenIdentifier(t *testing.T) {
type DummyValidationAuthority struct {
Called bool
Argument Authorization
Argument core.Authorization
}
func (dva *DummyValidationAuthority) UpdateValidations(authz Authorization) (err error) {
func (dva *DummyValidationAuthority) UpdateValidations(authz core.Authorization) (err error) {
dva.Called = true
dva.Argument = authz
return
@ -107,19 +111,19 @@ var (
}`)
AccountKey = jose.JsonWebKey{}
AuthzRequest = Authorization{
Identifier: AcmeIdentifier{
Type: IdentifierDNS,
AuthzRequest = core.Authorization{
Identifier: core.AcmeIdentifier{
Type: core.IdentifierDNS,
Value: "example.com",
},
}
AuthzDelta = Authorization{
Challenges: map[string]Challenge{
ChallengeTypeSimpleHTTPS: Challenge{
AuthzDelta = core.Authorization{
Challenges: map[string]core.Challenge{
core.ChallengeTypeSimpleHTTPS: core.Challenge{
Path: "Hf5GrX4Q7EBax9hc2jJnfw",
},
ChallengeTypeDVSNI: Challenge{
core.ChallengeTypeDVSNI: core.Challenge{
S: "23029d88d9e123e",
},
},
@ -128,14 +132,14 @@ var (
ExampleCSR = &x509.CertificateRequest{}
// These values are populated by the tests as we go
AuthzInitial = Authorization{}
AuthzUpdated = Authorization{}
AuthzFromVA = Authorization{}
AuthzFinal = Authorization{}
AuthzFinalWWW = Authorization{}
AuthzInitial = core.Authorization{}
AuthzUpdated = core.Authorization{}
AuthzFromVA = core.Authorization{}
AuthzFinal = core.Authorization{}
AuthzFinalWWW = core.Authorization{}
)
func initAuthorities(t *testing.T) (CertificateAuthority, *DummyValidationAuthority, *SQLStorageAuthority, RegistrationAuthority) {
func initAuthorities(t *testing.T) (core.CertificateAuthority, *DummyValidationAuthority, *SQLStorageAuthority, core.RegistrationAuthority) {
err := json.Unmarshal(AccountKeyJSON, &AccountKey)
AssertNotError(t, err, "Failed to unmarshall JWK")
@ -151,7 +155,7 @@ func initAuthorities(t *testing.T) (CertificateAuthority, *DummyValidationAuthor
caCertPEM, _ := pem.Decode([]byte(CA_CERT_PEM))
caCert, _ := x509.ParseCertificate(caCertPEM.Bytes)
signer, _ := local.NewSigner(caKey, caCert, x509.SHA256WithRSA, nil)
ca := CertificateAuthorityImpl{signer: signer, SA: sa}
ca := ca.CertificateAuthorityImpl{Signer: signer, SA: sa}
csrDER, _ := hex.DecodeString(CSR_HEX)
ExampleCSR, _ = x509.ParseCertificateRequest(csrDER)
@ -169,7 +173,7 @@ func assert(t *testing.T, test bool, message string) {
}
}
func assertAuthzEqual(t *testing.T, a1, a2 Authorization) {
func assertAuthzEqual(t *testing.T, a1, a2 core.Authorization) {
assert(t, a1.ID == a2.ID, "ret != DB: ID")
assert(t, a1.Identifier == a2.Identifier, "ret != DB: Identifier")
assert(t, a1.Status == a2.Status, "ret != DB: Status")
@ -191,11 +195,11 @@ func TestNewAuthorization(t *testing.T) {
// Verify that the returned authz has the right information
assert(t, authz.Key.Equals(AccountKey), "Initial authz did not get the right key")
assert(t, authz.Identifier == AuthzRequest.Identifier, "Initial authz had wrong identifier")
assert(t, authz.Status == StatusPending, "Initial authz not pending")
assert(t, authz.Status == core.StatusPending, "Initial authz not pending")
_, ok := authz.Challenges[ChallengeTypeDVSNI]
_, ok := authz.Challenges[core.ChallengeTypeDVSNI]
assert(t, ok, "Initial authz does not include DVSNI challenge")
_, ok = authz.Challenges[ChallengeTypeSimpleHTTPS]
_, ok = authz.Challenges[core.ChallengeTypeSimpleHTTPS]
assert(t, ok, "Initial authz does not include SimpleHTTPS challenge")
// If we get to here, we'll use this authorization for the next test
@ -224,12 +228,12 @@ func TestUpdateAuthorization(t *testing.T) {
assertAuthzEqual(t, authz, va.Argument)
// Verify that the responses are reflected
simpleHttps, ok := va.Argument.Challenges[ChallengeTypeSimpleHTTPS]
simpleHttpsOrig, _ := AuthzDelta.Challenges[ChallengeTypeSimpleHTTPS]
simpleHttps, ok := va.Argument.Challenges[core.ChallengeTypeSimpleHTTPS]
simpleHttpsOrig, _ := AuthzDelta.Challenges[core.ChallengeTypeSimpleHTTPS]
assert(t, ok, "Authz passed to VA has no simpleHttps challenge")
assert(t, simpleHttps.Path == simpleHttpsOrig.Path, "simpleHttps changed")
dvsni, ok := va.Argument.Challenges[ChallengeTypeDVSNI]
dvsniOrig, _ := AuthzDelta.Challenges[ChallengeTypeDVSNI]
dvsni, ok := va.Argument.Challenges[core.ChallengeTypeDVSNI]
dvsniOrig, _ := AuthzDelta.Challenges[core.ChallengeTypeDVSNI]
assert(t, ok, "Authz passed to VA has no dvsni challenge")
assert(t, dvsni.Token == dvsniOrig.Token, "dvsni changed")
@ -247,14 +251,14 @@ func TestOnValidationUpdate(t *testing.T) {
// Simulate a successful simpleHttps challenge
AuthzFromVA = AuthzUpdated
challenge := AuthzFromVA.Challenges[ChallengeTypeSimpleHTTPS]
challenge.Status = StatusValid
AuthzFromVA.Challenges[ChallengeTypeSimpleHTTPS] = challenge
challenge := AuthzFromVA.Challenges[core.ChallengeTypeSimpleHTTPS]
challenge.Status = core.StatusValid
AuthzFromVA.Challenges[core.ChallengeTypeSimpleHTTPS] = challenge
ra.OnValidationUpdate(AuthzFromVA)
// Verify that the Authz in the DB is the same except for Status->StatusValid
AuthzFromVA.Status = StatusValid
AuthzFromVA.Status = core.StatusValid
dbAuthz, err := sa.GetAuthorization(AuthzFromVA.ID)
AssertNotError(t, err, "Could not fetch authorization from database")
assertAuthzEqual(t, AuthzFromVA, dbAuthz)
@ -283,9 +287,9 @@ func TestNewCertificate(t *testing.T) {
// Construct a cert request referencing the two authorizations
url1, _ := url.Parse("http://doesnt.matter/" + AuthzFinal.ID)
url2, _ := url.Parse("http://doesnt.matter/" + AuthzFinalWWW.ID)
certRequest := CertificateRequest{
certRequest := core.CertificateRequest{
CSR: ExampleCSR,
Authorizations: []AcmeURL{AcmeURL(*url1), AcmeURL(*url2)},
Authorizations: []core.AcmeURL{core.AcmeURL(*url1), core.AcmeURL(*url2)},
}
cert, err := ra.NewCertificate(certRequest, AccountKey)
@ -299,3 +303,112 @@ func TestNewCertificate(t *testing.T) {
// TODO Test failure cases
t.Log("DONE TestOnValidationUpdate")
}
var CA_KEY_PEM = "-----BEGIN RSA PRIVATE KEY-----\n" +
"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\n" +
"-----END RSA PRIVATE KEY-----"
var CA_CERT_PEM = "-----BEGIN CERTIFICATE-----\n" +
"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\n" +
"-----END CERTIFICATE-----"
// CSR generated by Go:
// * Random public key
// * CN = example.com
// * DNSNames = example.com, www.example.com
var CSR_HEX = "308202953082017d0201003016311430120603" +
"550403130b6578616d706c652e636f6d30820122300d06092a864886f70d0101010500038201" +
"0f003082010a0282010100baaf16e891828470cad87b849a73356f65e20ad3699fd5583a7200" +
"e924512d9eeb1dbe16441ad7bd804fa2e5726a06f0af5279012fe6354a5677259f5591984aa9" +
"99b8ea3ea10fbd5ecfa30e5f563b41c419374decfc98ea62c611046ad011c326470a2426f46d" +
"be6cc44fae3b247e19710810585f9f3ad7f64b2f305aebb72e2829866f89b20b03a300b7ff5f" +
"4e6204f41420d9fa731252692cee8e616636723abe8a7053fd86e2673190fa8b618ada5bc735" +
"ba57a145af86904a8f55a288d4d6ba9e501530f23f197f5b623443193fc92b7f87d6abbf740d" +
"9fc92800c7e0e1484d5eec6ffae1007c139c1ec19d67e749743fe8d8396fe190cfbcf2f90e05" +
"230203010001a03a303806092a864886f70d01090e312b302930270603551d110420301e820b" +
"6578616d706c652e636f6d820f7777772e6578616d706c652e636f6d300d06092a864886f70d" +
"01010b05000382010100514c622dc866b31c86777a26e9b2911618ce5b188591f08007b42772" +
"3497b733676a7d493c434fc819b8089869442fd299aa99ff7f7b9df881843727f0c8b89ca62a" +
"f8a12b38c963e9210148c4e1c0fc964aef2605f88ed777e6497d2e43e9a9713835d1ae96260c" +
"ca826c34c7ae52c77f5d8468643ee1936eadf04e1ebf8bbbb68b0ec7d0ef694432451292e4a3" +
"1989fd8339c07e01e04b6dd3834872b828d3f5b2b4dadda0596396e15fbdf446998066a74873" +
"2baf53f3f7ebb849e83cf734753c35ab454d1b62e1741a6514c5c575c0c805b4d668fcf71746" +
"ef32017613a52d6b807e2977f4fbc0a88b2e263347c4d9e35435333cf4f8288be53df41228ec"