diff --git a/argon2.go b/argon2.go index dc5f4429..0ddf801c 100644 --- a/argon2.go +++ b/argon2.go @@ -109,7 +109,7 @@ type Argon2Options struct { Parallel uint8 } -func (o *Argon2Options) kdfParams(keyLen uint32) (*kdfParams, error) { +func (o *Argon2Options) kdfParams(defaultTargetDuration time.Duration, keyLen uint32) (*kdfParams, error) { switch o.Mode { case Argon2Default, Argon2i, Argon2id: // ok @@ -159,7 +159,7 @@ func (o *Argon2Options) kdfParams(keyLen uint32) (*kdfParams, error) { default: benchmarkParams := &argon2.BenchmarkParams{ MaxMemoryCostKiB: 1 * 1024 * 1024, // the default maximum memory cost is 1GiB. - TargetDuration: 2 * time.Second, // the default target duration is 2s. + TargetDuration: defaultTargetDuration, } if o.MemoryKiB != 0 { @@ -187,7 +187,7 @@ func (o *Argon2Options) kdfParams(keyLen uint32) (*kdfParams, error) { MemoryKiB: params.MemoryKiB, ForceIterations: params.Time, Parallel: params.Threads} - return o.kdfParams(keyLen) + return o.kdfParams(defaultTargetDuration, keyLen) } } diff --git a/argon2_test.go b/argon2_test.go index 016957c7..01ef4a3a 100644 --- a/argon2_test.go +++ b/argon2_test.go @@ -75,7 +75,7 @@ var _ = Suite(&argon2Suite{}) func (s *argon2Suite) TestKDFParamsDefault(c *C) { var opts Argon2Options - params, err := opts.KdfParams(0) + params, err := opts.KdfParams(2*time.Second, 0) c.Assert(err, IsNil) c.Check(s.kdf.BenchmarkMode, Equals, Argon2id) @@ -87,10 +87,24 @@ func (s *argon2Suite) TestKDFParamsDefault(c *C) { }) } +func (s *argon2Suite) TestKDFParamsDefaultWithDifferentTargetDuration(c *C) { + var opts Argon2Options + params, err := opts.KdfParams(200*time.Millisecond, 32) + c.Assert(err, IsNil) + c.Check(s.kdf.BenchmarkMode, Equals, Argon2id) + + c.Check(params, DeepEquals, &KdfParams{ + Type: "argon2id", + Time: 4, + Memory: 102406, + CPUs: s.cpusAuto, + }) +} + func (s *argon2Suite) TestKDFParamsExplicitMode(c *C) { var opts Argon2Options opts.Mode = Argon2i - params, err := opts.KdfParams(0) + params, err := opts.KdfParams(2*time.Second, 0) c.Assert(err, IsNil) c.Check(s.kdf.BenchmarkMode, Equals, Argon2i) @@ -105,7 +119,7 @@ func (s *argon2Suite) TestKDFParamsExplicitMode(c *C) { func (s *argon2Suite) TestKDFParamsTargetDuration(c *C) { var opts Argon2Options opts.TargetDuration = 1 * time.Second - params, err := opts.KdfParams(32) + params, err := opts.KdfParams(2*time.Second, 32) c.Assert(err, IsNil) c.Check(s.kdf.BenchmarkMode, Equals, Argon2id) @@ -120,7 +134,7 @@ func (s *argon2Suite) TestKDFParamsTargetDuration(c *C) { func (s *argon2Suite) TestKDFParamsMemoryLimit(c *C) { var opts Argon2Options opts.MemoryKiB = 32 * 1024 - params, err := opts.KdfParams(0) + params, err := opts.KdfParams(2*time.Second, 0) c.Assert(err, IsNil) c.Check(s.kdf.BenchmarkMode, Equals, Argon2id) @@ -135,7 +149,7 @@ func (s *argon2Suite) TestKDFParamsMemoryLimit(c *C) { func (s *argon2Suite) TestKDFParamsForceBenchmarkedThreads(c *C) { var opts Argon2Options opts.Parallel = 1 - params, err := opts.KdfParams(0) + params, err := opts.KdfParams(2*time.Second, 0) c.Assert(err, IsNil) c.Check(s.kdf.BenchmarkMode, Equals, Argon2id) @@ -153,7 +167,7 @@ func (s *argon2Suite) TestKDFParamsForceIterations(c *C) { var opts Argon2Options opts.ForceIterations = 3 - params, err := opts.KdfParams(0) + params, err := opts.KdfParams(2*time.Second, 0) c.Assert(err, IsNil) c.Check(s.kdf.BenchmarkMode, Equals, Argon2Default) @@ -172,7 +186,7 @@ func (s *argon2Suite) TestKDFParamsForceMemory(c *C) { var opts Argon2Options opts.ForceIterations = 3 opts.MemoryKiB = 32 * 1024 - params, err := opts.KdfParams(0) + params, err := opts.KdfParams(2*time.Second, 0) c.Assert(err, IsNil) c.Check(s.kdf.BenchmarkMode, Equals, Argon2Default) @@ -190,7 +204,7 @@ func (s *argon2Suite) TestKDFParamsForceIterationsDifferentCPUNum(c *C) { var opts Argon2Options opts.ForceIterations = 3 - params, err := opts.KdfParams(0) + params, err := opts.KdfParams(2*time.Second, 0) c.Assert(err, IsNil) c.Check(s.kdf.BenchmarkMode, Equals, Argon2Default) @@ -209,7 +223,7 @@ func (s *argon2Suite) TestKDFParamsForceThreads(c *C) { var opts Argon2Options opts.ForceIterations = 3 opts.Parallel = 1 - params, err := opts.KdfParams(9) + params, err := opts.KdfParams(2*time.Second, 0) c.Assert(err, IsNil) c.Check(s.kdf.BenchmarkMode, Equals, Argon2Default) @@ -228,7 +242,7 @@ func (s *argon2Suite) TestKDFParamsForceThreadsGreatherThanCPUNum(c *C) { var opts Argon2Options opts.ForceIterations = 3 opts.Parallel = 8 - params, err := opts.KdfParams(0) + params, err := opts.KdfParams(2*time.Second, 0) c.Assert(err, IsNil) c.Check(s.kdf.BenchmarkMode, Equals, Argon2Default) @@ -243,7 +257,7 @@ func (s *argon2Suite) TestKDFParamsForceThreadsGreatherThanCPUNum(c *C) { func (s *argon2Suite) TestKDFParamsInvalidForceIterations(c *C) { var opts Argon2Options opts.ForceIterations = math.MaxUint32 - _, err := opts.KdfParams(0) + _, err := opts.KdfParams(2*time.Second, 0) c.Check(err, ErrorMatches, `invalid iterations count 4294967295`) } @@ -251,7 +265,7 @@ func (s *argon2Suite) TestKDFParamsInvalidMemoryKiB(c *C) { var opts Argon2Options opts.ForceIterations = 4 opts.MemoryKiB = math.MaxUint32 - _, err := opts.KdfParams(0) + _, err := opts.KdfParams(2*time.Second, 0) c.Check(err, ErrorMatches, `invalid memory cost 4294967295KiB`) } diff --git a/export_test.go b/export_test.go index 0a5e6a6f..1030bb76 100644 --- a/export_test.go +++ b/export_test.go @@ -43,12 +43,12 @@ type ( ProtectedKeys = protectedKeys ) -func (o *Argon2Options) KdfParams(keyLen uint32) (*KdfParams, error) { - return o.kdfParams(keyLen) +func (o *Argon2Options) KdfParams(defaultTargetDuration time.Duration, keyLen uint32) (*KdfParams, error) { + return o.kdfParams(defaultTargetDuration, keyLen) } -func (o *PBKDF2Options) KdfParams(keyLen uint32) (*KdfParams, error) { - return o.kdfParams(keyLen) +func (o *PBKDF2Options) KdfParams(defaultTargetDuration time.Duration, keyLen uint32) (*KdfParams, error) { + return o.kdfParams(defaultTargetDuration, keyLen) } func MockLUKS2Activate(fn func(string, string, []byte, int) error) (restore func()) { diff --git a/kdf.go b/kdf.go index 70fc9c6d..264fbc13 100644 --- a/kdf.go +++ b/kdf.go @@ -19,8 +19,10 @@ package secboot +import "time" + // KDFOptions is an interface for supplying options for different // key derivation functions type KDFOptions interface { - kdfParams(keyLen uint32) (*kdfParams, error) + kdfParams(defaultTargetDuration time.Duration, keyLen uint32) (*kdfParams, error) } diff --git a/keydata.go b/keydata.go index 33554f48..38cb909c 100644 --- a/keydata.go +++ b/keydata.go @@ -30,6 +30,7 @@ import ( "fmt" "hash" "io" + "time" "github.com/snapcore/secboot/internal/pbkdf2" "golang.org/x/crypto/cryptobyte" @@ -125,6 +126,7 @@ type AuthMode uint8 const ( AuthModeNone AuthMode = iota AuthModePassphrase + AuthModePIN ) // KeyParams provides parameters required to create a new KeyData object. @@ -167,6 +169,15 @@ type KeyWithPassphraseParams struct { AuthKeySize int } +type KeyWithPINParams struct { + KeyParams + KDFOptions *PBKDF2Options // The PIN KDF options + + // AuthKeySize is the size of key to derive from the PIN for + // use by the platform implementation. + AuthKeySize int +} + // KeyID is the unique ID for a KeyData object. It is used to facilitate the // sharing of state between the early boot environment and OS runtime. type KeyID []byte @@ -302,6 +313,11 @@ type passphraseParams struct { AuthKeySize int `json:"auth_key_size"` // Size of auth key to derive from passphrase derived key } +type pinParams struct { + KDF kdfData `json:"kdf"` + AuthKeySize int `json:"auth_key_size"` +} + type keyData struct { // Generation is a number used to differentiate between different key formats. // i.e Gen1 keys are binary serialized and include a primary and an unlock key while @@ -336,6 +352,7 @@ type keyData struct { EncryptedPayload []byte `json:"encrypted_payload"` PassphraseParams *passphraseParams `json:"passphrase_params,omitempty"` + PINParams *pinParams `json:"pin_params,omitempty"` // AuthorizedSnapModels contains information about the Snap models // that have been authorized to access the data protected by this key. @@ -394,7 +411,9 @@ func (d *KeyData) derivePassphraseKeys(passphrase string) (key, iv, auth []byte, return nil, nil, nil, fmt.Errorf("unavailable leaf KDF digest algorithm %v", kdfAlg) } - // Include derivation parameters in the Argon2 salt in order to protect them + // Include derivation parameters in the KDF salt in order to protect them. + // Ideally the extra parameters would be part of Argon2's additional data, but + // the go package doesn't expose this. builder := cryptobyte.NewBuilder(nil) builder.AddASN1(cryptobyte_asn1.SEQUENCE, func(b *cryptobyte.Builder) { // SEQUENCE { b.AddASN1OctetString(params.KDF.Salt) // salt OCTET STRING @@ -467,6 +486,36 @@ func (d *KeyData) derivePassphraseKeys(passphrase string) (key, iv, auth []byte, return key, iv, auth, nil } +func (d *KeyData) derivePINAuthKey(pin PIN) ([]byte, error) { + if d.data.PINParams == nil { + return nil, errors.New("no PIN params") + } + + params := d.data.PINParams + if params.AuthKeySize < 0 { + return nil, fmt.Errorf("invalid auth key size (%d bytes)", params.AuthKeySize) + } + if params.KDF.Time < 0 { + return nil, fmt.Errorf("invalid KDF time (%d)", params.KDF.Time) + } + if params.KDF.Type != pbkdf2Type { + return nil, fmt.Errorf("unexpected KDF type \"%s\"", params.KDF.Type) + } + + pbkdfParams := &pbkdf2.Params{ + Iterations: uint(params.KDF.Time), + HashAlg: crypto.Hash(params.KDF.Hash), + } + if !pbkdfParams.HashAlg.Available() { + return nil, fmt.Errorf("unavailable pbkdf2 digest algorithm %v", pbkdfParams.HashAlg) + } + key, err := pbkdf2.Key(string(pin.Bytes()), params.KDF.Salt, pbkdfParams, uint(params.AuthKeySize)) + if err != nil { + return nil, xerrors.Errorf("cannot derive auth key from PIN: %w", err) + } + return key, nil +} + func (d *KeyData) updatePassphrase(payload, oldAuthKey []byte, passphrase string) error { handler := handlers[d.data.PlatformName] if handler == nil { @@ -502,6 +551,26 @@ func (d *KeyData) updatePassphrase(payload, oldAuthKey []byte, passphrase string return nil } +func (d *KeyData) updatePIN(oldAuthKey []byte, pin PIN) error { + handler := handlers[d.data.PlatformName] + if handler == nil { + return ErrNoPlatformHandlerRegistered + } + + authKey, err := d.derivePINAuthKey(pin) + if err != nil { + return err + } + + handle, err := handler.ChangeAuthKey(d.platformKeyData(), oldAuthKey, authKey) + if err != nil { + return err + } + + d.data.PlatformHandle = handle + return nil +} + func (d *KeyData) openWithPassphrase(passphrase string) (payload []byte, authKey []byte, err error) { key, iv, authKey, err := d.derivePassphraseKeys(passphrase) if err != nil { @@ -594,6 +663,8 @@ func (d *KeyData) AuthMode() (out AuthMode) { switch { case d.data.PassphraseParams != nil: return AuthModePassphrase + case d.data.PINParams != nil: + return AuthModePIN default: return AuthModeNone } @@ -683,6 +754,29 @@ func (d *KeyData) RecoverKeysWithPassphrase(passphrase string) (DiskUnlockKey, P return d.recoverKeysCommon(c) } +func (d *KeyData) RecoverKeysWithPIN(pin PIN) (DiskUnlockKey, PrimaryKey, error) { + if d.AuthMode() != AuthModePIN { + return nil, nil, errors.New("cannot recover key with PIN") + } + + handler := handlers[d.data.PlatformName] + if handler == nil { + return nil, nil, ErrNoPlatformHandlerRegistered + } + + key, err := d.derivePINAuthKey(pin) + if err != nil { + return nil, nil, err + } + + c, err := handler.RecoverKeysWithAuthKey(d.platformKeyData(), d.data.EncryptedPayload, key) + if err != nil { + return nil, nil, processPlatformHandlerError(err) + } + + return d.recoverKeysCommon(c) +} + // ChangePassphrase updates the passphrase used to recover the keys from this key data // via the KeyData.RecoverKeysWithPassphrase API. This can only be called if a passhphrase // has been set previously (KeyData.AuthMode returns AuthModePassphrase). @@ -705,6 +799,23 @@ func (d *KeyData) ChangePassphrase(oldPassphrase, newPassphrase string) error { return nil } +func (d *KeyData) ChangePIN(oldPIN, newPIN PIN) error { + if d.AuthMode()&AuthModePIN == 0 { + return errors.New("cannot change PIN without setting an initial PIN") + } + + oldKey, err := d.derivePINAuthKey(oldPIN) + if err != nil { + return err + } + + if err := d.updatePIN(oldKey, newPIN); err != nil { + return processPlatformHandlerError(err) + } + + return nil +} + // WriteAtomic saves this key data to the supplied KeyDataWriter. func (d *KeyData) WriteAtomic(w KeyDataWriter) error { enc := json.NewEncoder(w) @@ -771,7 +882,7 @@ func NewKeyDataWithPassphrase(params *KeyWithPassphraseParams, passphrase string kdfOptions = &defaultOptions } - kdfParams, err := kdfOptions.kdfParams(passphraseKeyLen) + kdfParams, err := kdfOptions.kdfParams(2*time.Second, passphraseKeyLen) if err != nil { return nil, xerrors.Errorf("cannot derive KDF cost parameters: %w", err) } @@ -799,6 +910,47 @@ func NewKeyDataWithPassphrase(params *KeyWithPassphraseParams, passphrase string return kd, nil } +func NewKeyDataWithPIN(params *KeyWithPINParams, pin PIN) (*KeyData, error) { + kd, err := NewKeyData(¶ms.KeyParams) + if err != nil { + return nil, err + } + + kdfOptions := params.KDFOptions + if kdfOptions == nil { + var defaultOptions PBKDF2Options + kdfOptions = &defaultOptions + } + + if params.AuthKeySize < 0 { + return nil, errors.New("invalid auth key size") + } + + kdfParams, err := kdfOptions.kdfParams(200*time.Millisecond, uint32(params.AuthKeySize)) + if err != nil { + return nil, xerrors.Errorf("cannot derive KDF cost parameters: %w", err) + } + + var salt [16]byte + if _, err := rand.Read(salt[:]); err != nil { + return nil, xerrors.Errorf("cannot read salt: %w", err) + } + + kd.data.PINParams = &pinParams{ + KDF: kdfData{ + Salt: salt[:], + kdfParams: *kdfParams, + }, + AuthKeySize: params.AuthKeySize, + } + + if err := kd.updatePIN(make([]byte, params.AuthKeySize), pin); err != nil { + return nil, xerrors.Errorf("cannot set PIN: %w", err) + } + + return kd, nil +} + // protectedKeys is used to pack a primary key and a unique value from which // an unlock key is derived. type protectedKeys struct { diff --git a/keydata_test.go b/keydata_test.go index 03ba13a7..54ea1559 100644 --- a/keydata_test.go +++ b/keydata_test.go @@ -435,7 +435,7 @@ func (s *keyDataTestBase) checkKeyDataJSONDecodedAuthModePassphrase(c *C, j map[ kdfOpts = &def } - kdfParams, err := kdfOpts.KdfParams(0) + kdfParams, err := kdfOpts.KdfParams(2*time.Second, 0) c.Assert(err, IsNil) s.checkKeyDataJSONCommon(c, j, &creationParams.KeyParams, nmodels) diff --git a/pbkdf2.go b/pbkdf2.go index 72aa5bb4..026b4933 100644 --- a/pbkdf2.go +++ b/pbkdf2.go @@ -46,7 +46,7 @@ type PBKDF2Options struct { HashAlg crypto.Hash } -func (o *PBKDF2Options) kdfParams(keyLen uint32) (*kdfParams, error) { +func (o *PBKDF2Options) kdfParams(defaultTargetDuration time.Duration, keyLen uint32) (*kdfParams, error) { if keyLen > math.MaxInt32 { return nil, errors.New("invalid key length") } @@ -84,7 +84,7 @@ func (o *PBKDF2Options) kdfParams(keyLen uint32) (*kdfParams, error) { return params, nil default: - targetDuration := 2 * time.Second // the default target duration is 2s. + targetDuration := defaultTargetDuration hashAlg := defaultHashAlg if o.TargetDuration != 0 { @@ -102,6 +102,6 @@ func (o *PBKDF2Options) kdfParams(keyLen uint32) (*kdfParams, error) { o = &PBKDF2Options{ ForceIterations: uint32(iterations), HashAlg: hashAlg} - return o.kdfParams(keyLen) + return o.kdfParams(defaultTargetDuration, keyLen) } } diff --git a/pbkdf2_test.go b/pbkdf2_test.go index 7ec1479c..1ec7aca6 100644 --- a/pbkdf2_test.go +++ b/pbkdf2_test.go @@ -67,7 +67,14 @@ func (s *pbkdf2Suite) TestKDFParamsDefault(c *C) { defer restore() var opts PBKDF2Options - params, err := opts.KdfParams(32) + params, err := opts.KdfParams(2*time.Second, 32) + c.Assert(err, IsNil) + s.checkParams(c, &opts, 32, params) +} + +func (s *pbkdf2Suite) TestKDFParamsDefaultWithDifferentTargetDuration(c *C) { + var opts PBKDF2Options + params, err := opts.KdfParams(200*time.Millisecond, 32) c.Assert(err, IsNil) c.Check(params.Type, Equals, "pbkdf2") c.Check(params.Hash, Equals, HashAlg(crypto.SHA256)) @@ -84,7 +91,7 @@ func (s *pbkdf2Suite) TestKDFParamsDefault48(c *C) { defer restore() var opts PBKDF2Options - params, err := opts.KdfParams(48) + params, err := opts.KdfParams(2*time.Second, 48) c.Assert(err, IsNil) c.Check(params.Type, Equals, "pbkdf2") c.Check(params.Hash, Equals, HashAlg(crypto.SHA384)) @@ -101,7 +108,7 @@ func (s *pbkdf2Suite) TestKDFParamsDefault64(c *C) { defer restore() var opts PBKDF2Options - params, err := opts.KdfParams(64) + params, err := opts.KdfParams(2*time.Second, 64) c.Assert(err, IsNil) c.Check(params.Type, Equals, "pbkdf2") c.Check(params.Hash, Equals, HashAlg(crypto.SHA512)) @@ -119,7 +126,7 @@ func (s *pbkdf2Suite) TestKDFParamsTargetDuration(c *C) { var opts PBKDF2Options opts.TargetDuration = 200 * time.Millisecond - params, err := opts.KdfParams(32) + params, err := opts.KdfParams(2*time.Second, 32) c.Assert(err, IsNil) c.Check(params.Type, Equals, "pbkdf2") c.Check(params.Hash, Equals, HashAlg(crypto.SHA256)) @@ -130,7 +137,7 @@ func (s *pbkdf2Suite) TestKDFParamsTargetDuration(c *C) { func (s *pbkdf2Suite) TestKDFParamsForceIterations(c *C) { var opts PBKDF2Options opts.ForceIterations = 2000 - params, err := opts.KdfParams(32) + params, err := opts.KdfParams(2*time.Second, 32) c.Assert(err, IsNil) c.Check(params, DeepEquals, &KdfParams{ Type: "pbkdf2", @@ -149,7 +156,7 @@ func (s *pbkdf2Suite) TestKDFParamsCustomHash(c *C) { var opts PBKDF2Options opts.HashAlg = crypto.SHA512 - params, err := opts.KdfParams(32) + params, err := opts.KdfParams(2*time.Second, 32) c.Assert(err, IsNil) c.Check(params.Type, Equals, "pbkdf2") c.Check(params.Hash, Equals, HashAlg(crypto.SHA512)) diff --git a/pin.go b/pin.go new file mode 100644 index 00000000..e4a16e0b --- /dev/null +++ b/pin.go @@ -0,0 +1,63 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2024 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package secboot + +import ( + "errors" + "fmt" + "math" + "math/big" +) + +type PIN struct { + length uint8 // the length of the input PIN. This is *not* the length of the encoded binary number + value big.Int // the PIN value. This is encoded in big-endian form without leading zeroes. +} + +func ParsePIN(s string) (PIN, error) { + l := len(s) + if l > math.MaxUint8 { + return PIN{}, errors.New("invalid PIN: too long") + } + + val, ok := new(big.Int).SetString(s, 10) + if !ok { + return PIN{}, errors.New("invalid PIN") + } + + return PIN{ + length: uint8(l), + value: *val, + }, nil +} + +func (p PIN) String() string { + return fmt.Sprintf("%0*s", p.length, p.value.String()) +} + +func (p PIN) Bytes() []byte { + maxS := make([]byte, p.length) + for i := range maxS { + maxS[i] = '9' + } + max, _ := new(big.Int).SetString(string(maxS), 10) + b := make([]byte, len(max.Bytes())) + return append([]byte{p.length}, p.value.FillBytes(b)...) +} diff --git a/pin_test.go b/pin_test.go new file mode 100644 index 00000000..a254e66e --- /dev/null +++ b/pin_test.go @@ -0,0 +1,101 @@ +package secboot_test + +import ( + . "github.com/snapcore/secboot" + "github.com/snapcore/secboot/internal/testutil" + + . "gopkg.in/check.v1" +) + +type pinSuite struct{} + +var _ = Suite(&pinSuite{}) + +func (s *pinSuite) TestPIN(c *C) { + pin, err := ParsePIN("1234") + c.Assert(err, IsNil) + + c.Check(pin.String(), Equals, "1234") + c.Check(pin.Bytes(), DeepEquals, testutil.DecodeHexString(c, "0404d2")) +} + +func (s *pinSuite) TestPINZeroPaddedIsDifferent(c *C) { + pin, err := ParsePIN("00001234") + c.Assert(err, IsNil) + + c.Check(pin.String(), Equals, "00001234") + c.Check(pin.Bytes(), DeepEquals, testutil.DecodeHexString(c, "08000004d2")) +} + +func (s *pinSuite) TestPIN2(c *C) { + pin, err := ParsePIN("12345678") + c.Assert(err, IsNil) + + c.Check(pin.String(), Equals, "12345678") + c.Check(pin.Bytes(), DeepEquals, testutil.DecodeHexString(c, "0800bc614e")) +} + +func (s *pinSuite) TestPIN3(c *C) { + pin, err := ParsePIN("00000000") + c.Assert(err, IsNil) + + c.Check(pin.String(), Equals, "00000000") + c.Check(pin.Bytes(), DeepEquals, testutil.DecodeHexString(c, "0800000000")) +} + +func (s *pinSuite) TestPIN4(c *C) { + pin, err := ParsePIN("99999999") + c.Assert(err, IsNil) + + c.Check(pin.String(), Equals, "99999999") + c.Check(pin.Bytes(), DeepEquals, testutil.DecodeHexString(c, "0805f5e0ff")) +} + +func (s *pinSuite) TestPIN5(c *C) { + pin, err := ParsePIN("246813") + c.Assert(err, IsNil) + + c.Check(pin.String(), Equals, "246813") + c.Check(pin.Bytes(), DeepEquals, testutil.DecodeHexString(c, "0603c41d")) +} + +func (s *pinSuite) TestPINLongest(c *C) { + pin, err := ParsePIN("1234567812345678123456781234567812345678123456781234567812345678" + + "12345678123456781234567812345678123456781234567812345678123456781234567812345678" + + "12345678123456781234567812345678123456781234567812345678123456781234567812345678" + + "1234567812345678123456781234567") + c.Assert(err, IsNil) + + c.Check(pin.String(), Equals, "1234567812345678123456781234567812345678123456781234567812345678"+ + "12345678123456781234567812345678123456781234567812345678123456781234567812345678"+ + "12345678123456781234567812345678123456781234567812345678123456781234567812345678"+ + "1234567812345678123456781234567") + c.Check(pin.Bytes(), DeepEquals, testutil.DecodeHexString(c, "ff10d6ce8940392078ffd0aa1ced339ebd632df03586ebc7a964a198aa06dfecf4417552290933dd874c2e00f55ea5ba5c1d4bea13735a8c5fc9edfbdb473a2df4dda455f0c098d6c0d592a7cb42a5383e7b9a34b3d5b8ccde89851ecf645becf69d528a2af48c8b923187")) +} + +func (s *pinSuite) TestPINMax(c *C) { + pin, err := ParsePIN("9999999999999999999999999999999999999999999999999999999999999999" + + "99999999999999999999999999999999999999999999999999999999999999999999999999999999" + + "99999999999999999999999999999999999999999999999999999999999999999999999999999999" + + "9999999999999999999999999999999") + c.Assert(err, IsNil) + + c.Check(pin.String(), Equals, "9999999999999999999999999999999999999999999999999999999999999999"+ + "99999999999999999999999999999999999999999999999999999999999999999999999999999999"+ + "99999999999999999999999999999999999999999999999999999999999999999999999999999999"+ + "9999999999999999999999999999999") + c.Check(pin.Bytes(), DeepEquals, testutil.DecodeHexString(c, "ff8865899617fb18717e2fa67c7a658892d0e50a3297e8c7a2252cd6ccbb9b0606aebc361bb89d4493d7119d783e8b155bc8ce61877171a4630813ce9bb7f3fc15c32513152722c26b0c667fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) +} + +func (s *pinSuite) TestPINTooLong(c *C) { + _, err := ParsePIN("1234567812345678123456781234567812345678123456781234567812345678" + + "12345678123456781234567812345678123456781234567812345678123456781234567812345678" + + "12345678123456781234567812345678123456781234567812345678123456781234567812345678" + + "12345678123456781234567812345678") + c.Check(err, ErrorMatches, `invalid PIN: too long`) +} + +func (s *pinSuite) TestPINInvalidChars(c *C) { + _, err := ParsePIN("1234abc") + c.Check(err, ErrorMatches, `invalid PIN`) +}