Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
IRMA
Github mirrors
irmago
Commits
1f6763ab
Commit
1f6763ab
authored
Jul 24, 2017
by
Sietse Ringers
Browse files
Add credential loading
parent
205d5943
Changes
3
Hide whitespace changes
Inline
Side-by-side
irmago_test.go
View file @
1f6763ab
...
...
@@ -2,24 +2,69 @@ package irmago
import
"testing"
import
(
"os"
"fmt"
"github.com/mhe/gabi"
"github.com/stretchr/testify/assert"
)
func
TestAndroidParse
(
t
*
testing
.
T
)
{
gabi
.
MetaStore
.
ParseFolder
(
"testdata/irma_configuration"
)
Manager
.
Init
(
"testdata/storage"
)
err
:=
Manager
.
ParseAndroidStorage
()
func
TestMain
(
m
*
testing
.
M
)
{
if
len
(
gabi
.
MetaStore
.
SchemeManagers
)
==
0
{
// FIXME
gabi
.
MetaStore
.
ParseFolder
(
"testdata/irma_configuration"
)
}
Manager
=
newCredentialManager
()
err
:=
os
.
RemoveAll
(
"testdata/storage/test"
)
if
err
!=
nil
{
fmt
.
Errorf
(
"Could not delete test storage"
)
os
.
Exit
(
1
)
}
err
=
os
.
Mkdir
(
"testdata/storage/test"
,
0755
)
if
err
!=
nil
{
fmt
.
Errorf
(
"Could not create test storage"
)
os
.
Exit
(
1
)
}
retCode
:=
m
.
Run
()
err
=
os
.
RemoveAll
(
"testdata/storage/test"
)
if
err
!=
nil
{
fmt
.
Errorf
(
"Could not delete test storage"
)
os
.
Exit
(
1
)
}
os
.
Exit
(
retCode
)
}
func
parseAndroidStorage
(
t
*
testing
.
T
)
{
err
:=
Manager
.
Init
(
"testdata/storage/test"
)
assert
.
NoError
(
t
,
err
,
"Manager.Init() failed"
)
err
=
Manager
.
ParseAndroidStorage
()
assert
.
NoError
(
t
,
err
,
"ParseAndroidStorage failed"
)
assert
.
NotEmpty
(
t
,
Manager
.
credentials
,
"No credentials deserialized"
)
assert
.
Contains
(
t
,
Manager
.
credentials
,
"irma-demo.RU.studentCard"
,
"irma-demo.RU.studentCard not deserialized"
)
assert
.
NotEmpty
(
t
,
Manager
.
credentials
,
"irma-demo.RU.studentCard not deserialized"
)
cred
:=
Manager
.
credentials
[
"irma-demo.RU.studentCard"
][
0
]
}
func
verifyStoreIsUnmarshaled
(
t
*
testing
.
T
)
{
cred
,
err
:=
Manager
.
Credential
(
"irma-demo.RU.studentCard"
,
0
)
assert
.
NoError
(
t
,
err
,
"could not fetch credential"
)
assert
.
NotNil
(
t
,
cred
,
"Credential should exist"
)
assert
.
NotNil
(
t
,
cred
.
Attributes
[
0
],
"Metadata attribute of irma-demo.RU.studentCard should not be nil"
)
assert
.
True
(
t
,
Manager
.
credentials
[
"irma-demo.RU.studentCard"
][
0
]
.
Signature
.
Verify
(
cred
.
PublicKey
(),
cred
.
Attributes
),
cred
.
Signature
.
Verify
(
cred
.
PublicKey
(),
cred
.
Attributes
),
"Credential should be valid"
,
)
}
func
TestAndroidParse
(
t
*
testing
.
T
)
{
parseAndroidStorage
(
t
)
verifyStoreIsUnmarshaled
(
t
)
}
func
TestUnmarshaling
(
t
*
testing
.
T
)
{
parseAndroidStorage
(
t
)
Manager
=
newCredentialManager
()
Manager
.
Init
(
"testdata/storage/test"
)
verifyStoreIsUnmarshaled
(
t
)
}
manager.go
View file @
1f6763ab
...
...
@@ -12,41 +12,87 @@ import (
)
// Manager is the global instance of CredentialManager.
var
Manager
=
CredentialManager
{
attributes
:
make
(
map
[
string
][]
*
AttributeList
),
credentials
:
make
(
map
[
string
][]
*
gabi
.
Credential
),
}
var
Manager
=
newCredentialManager
()
// CredentialManager manages credentials.
type
CredentialManager
struct
{
secretkey
*
big
.
Int
storagePath
string
attributes
map
[
string
][]
*
AttributeList
credentials
map
[
string
]
[
]
*
gabi
.
Credential
credentials
map
[
string
]
map
[
int
]
*
gabi
.
Credential
}
func
(
cm
*
CredentialManager
)
generateSecretKey
()
(
err
error
)
{
cm
.
secretkey
,
err
=
gabi
.
RandomBigInt
(
gabi
.
DefaultSystemParameters
[
1024
]
.
Lm
)
return
func
newCredentialManager
()
*
CredentialManager
{
return
&
CredentialManager
{
credentials
:
make
(
map
[
string
]
map
[
int
]
*
gabi
.
Credential
),
}
}
func
(
cm
*
CredentialManager
)
generateSecretKey
()
(
sk
*
big
.
Int
,
err
error
)
{
return
gabi
.
RandomBigInt
(
gabi
.
DefaultSystemParameters
[
1024
]
.
Lm
)
}
// Init deserializes the credentials from storage.
func
(
cm
*
CredentialManager
)
Init
(
path
string
)
(
err
error
)
{
cm
.
storagePath
=
path
cm
.
ensureStorageExists
()
bytes
,
err
:=
ioutil
.
ReadFile
(
cm
.
path
(
skFile
))
err
=
cm
.
ensureStorageExists
()
if
err
!=
nil
{
return
err
}
cm
.
secretkey
,
err
=
cm
.
loadSecretKey
()
if
err
!=
nil
{
return
}
cm
.
secretkey
=
new
(
big
.
Int
)
.
SetBytes
(
bytes
)
cm
.
attributes
,
err
=
cm
.
loadAttributes
()
return
}
// Attributes returns the attribute list of the requested credential, or nil if we do not have it.
func
(
cm
*
CredentialManager
)
Attributes
(
id
string
,
counter
int
)
(
attributes
*
AttributeList
)
{
list
,
exists
:=
cm
.
attributes
[
id
]
if
!
exists
||
len
(
list
)
<=
counter
{
return
}
return
list
[
counter
]
}
// Credential returns the requested credential, or nil if we do not have it.
func
(
cm
*
CredentialManager
)
Credential
(
id
string
,
counter
int
)
(
cred
*
gabi
.
Credential
,
err
error
)
{
_
,
exists
:=
cm
.
credentials
[
id
]
if
!
exists
{
cm
.
credentials
[
id
]
=
make
(
map
[
int
]
*
gabi
.
Credential
)
}
// If the requested credential is not in credential map, we check if its attributes were
// deserialized during Init(). If so, there should be a corresponding signature file,
// so we read that, construct the credential, and add it to the credential map
if
_
,
exists
:=
cm
.
credentials
[
id
][
counter
];
!
exists
{
attrs
:=
cm
.
Attributes
(
id
,
counter
)
if
attrs
==
nil
{
// We do not have the requested cred
return
}
ints
:=
append
([]
*
big
.
Int
{
cm
.
secretkey
},
attrs
.
Ints
...
)
sig
,
err
:=
cm
.
loadSignature
(
id
,
counter
)
if
err
!=
nil
{
return
nil
,
err
}
if
sig
==
nil
{
err
=
errors
.
New
(
"signature file not found"
)
return
nil
,
err
}
cred
:=
gabi
.
NewCredential
(
ints
,
sig
,
nil
)
cm
.
credentials
[
id
][
counter
]
=
cred
}
return
cm
.
credentials
[
id
][
counter
],
nil
}
// ParseAndroidStorage parses an Android cardemu.xml shared preferences file
// from the old Android IRMA app, parsing its credentials into the current instance.
// from the old Android IRMA app, parsing its credentials into the current instance,
// and saving them to storage.
// CAREFUL: this method overwrites any existing secret keys and attributes on storage.
func
(
cm
*
CredentialManager
)
ParseAndroidStorage
()
(
err
error
)
{
exists
,
err
:=
pathExists
(
cm
.
path
(
cardemuXML
))
if
err
!=
nil
||
!
exists
{
...
...
@@ -54,27 +100,26 @@ func (cm *CredentialManager) ParseAndroidStorage() (err error) {
}
bytes
,
err
:=
ioutil
.
ReadFile
(
cm
.
path
(
cardemuXML
))
parsed
:=
struct
{
parsed
xml
:=
struct
{
Strings
[]
struct
{
Name
string
`xml:"name,attr"`
Content
string
`xml:",chardata"`
}
`xml:"string"`
}{}
xml
.
Unmarshal
(
bytes
,
&
parsed
)
xml
.
Unmarshal
(
bytes
,
&
parsed
xml
)
for
_
,
xmltag
:=
range
parsed
.
Strings
{
parsedjson
:=
make
(
map
[
string
][]
*
gabi
.
Credential
)
for
_
,
xmltag
:=
range
parsedxml
.
Strings
{
if
xmltag
.
Name
==
"credentials"
{
jsontag
:=
html
.
UnescapeString
(
xmltag
.
Content
)
if
err
=
json
.
Unmarshal
([]
byte
(
jsontag
),
&
cm
.
credentials
);
err
!=
nil
{
if
err
=
json
.
Unmarshal
([]
byte
(
jsontag
),
&
parsedjson
);
err
!=
nil
{
return
}
}
}
for
_
,
list
:=
range
cm
.
credentials
{
if
list
!=
nil
&&
len
(
list
)
>
0
{
cm
.
secretkey
=
list
[
0
]
.
Attributes
[
0
]
}
for
_
,
list
:=
range
parsedjson
{
cm
.
secretkey
=
list
[
0
]
.
Attributes
[
0
]
for
i
,
cred
:=
range
list
{
// TODO move this metadata initialisation somehow into gabi.Credential?
cred
.
MetadataAttribute
=
gabi
.
MetadataFromInt
(
cred
.
Attributes
[
1
])
...
...
@@ -95,7 +140,7 @@ func (cm *CredentialManager) ParseAndroidStorage() (err error) {
if
err
!=
nil
{
return
err
}
err
=
cm
.
store
Key
(
)
err
=
cm
.
store
SecretKey
(
cm
.
secretkey
)
if
err
!=
nil
{
return
err
}
...
...
@@ -112,9 +157,10 @@ func (cm *CredentialManager) addCredential(cred *gabi.Credential) {
cm
.
attributes
[
id
]
=
append
(
cm
.
attributes
[
id
],
NewAttributeListFromInts
(
cred
.
Attributes
[
1
:
]))
if
_
,
exists
:=
cm
.
credentials
[
id
];
!
exists
{
cm
.
credentials
[
id
]
=
make
(
[
]
*
gabi
.
Credential
,
0
,
1
)
cm
.
credentials
[
id
]
=
make
(
map
[
int
]
*
gabi
.
Credential
)
}
cm
.
credentials
[
id
]
=
append
(
cm
.
credentials
[
id
],
cred
)
counter
:=
len
(
cm
.
attributes
[
id
])
-
1
cm
.
credentials
[
id
][
counter
]
=
cred
}
// Add adds the specified credential to the CredentialManager.
...
...
@@ -124,7 +170,7 @@ func (cm *CredentialManager) Add(cred *gabi.Credential) (err error) {
}
cm
.
addCredential
(
cred
)
counter
:=
len
(
cm
.
credentials
)
-
1
counter
:=
len
(
cm
.
credentials
[
cred
.
CredentialType
()
.
Identifier
()]
)
-
1
err
=
cm
.
storeSignature
(
cred
,
counter
)
if
err
!=
nil
{
...
...
storage.go
View file @
1f6763ab
...
...
@@ -7,7 +7,11 @@ import (
"os"
"strconv"
"crypto/rand"
"encoding/hex"
"github.com/mhe/gabi"
"math/big"
"path"
)
// Filenames in which we store stuff
...
...
@@ -15,7 +19,7 @@ const (
skFile
=
"sk"
attributesFile
=
"attrs"
signaturesDir
=
"sigs"
cardemuXML
=
"cardemu.xml"
cardemuXML
=
"
../
cardemu.xml"
)
func
pathExists
(
path
string
)
(
bool
,
error
)
{
...
...
@@ -33,6 +37,15 @@ func (cm *CredentialManager) path(file string) string {
return
cm
.
storagePath
+
"/"
+
file
}
func
(
cm
*
CredentialManager
)
signatureFilename
(
id
string
,
counter
int
)
string
{
return
cm
.
path
(
signaturesDir
)
+
"/"
+
id
+
"-"
+
strconv
.
Itoa
(
counter
)
}
// ensureStorageExists initializes the credential storage folder,
// ensuring that it is in a usable state.
// NOTE: we do not create the folder if it does not exist!
// Setting it up in a properly protected location (e.g., with automatic
// backups to iCloud/Google disabled) is the responsibility of the user.
func
(
cm
*
CredentialManager
)
ensureStorageExists
()
(
err
error
)
{
exist
,
err
:=
pathExists
(
cm
.
storagePath
)
if
err
!=
nil
{
...
...
@@ -42,43 +55,6 @@ func (cm *CredentialManager) ensureStorageExists() (err error) {
return
errors
.
New
(
"credential storage path does not exist"
)
}
var
file
*
os
.
File
exist
,
err
=
pathExists
(
cm
.
path
(
skFile
))
if
err
!=
nil
{
return
}
if
!
exist
{
err
=
cm
.
generateSecretKey
()
if
err
!=
nil
{
return
}
file
,
err
=
os
.
Create
(
cm
.
path
(
skFile
))
if
err
!=
nil
{
return
}
defer
file
.
Close
()
_
,
err
=
file
.
Write
(
cm
.
secretkey
.
Bytes
())
if
err
!=
nil
{
return
}
}
exist
,
err
=
pathExists
(
cm
.
path
(
attributesFile
))
if
err
!=
nil
{
return
err
}
if
!
exist
{
file
,
err
=
os
.
Create
(
cm
.
path
(
attributesFile
))
if
err
!=
nil
{
return
}
defer
file
.
Close
()
_
,
err
=
file
.
Write
([]
byte
(
"{}"
))
if
err
!=
nil
{
return
}
}
exist
,
err
=
pathExists
(
cm
.
path
(
signaturesDir
))
if
err
!=
nil
{
return
err
...
...
@@ -90,8 +66,32 @@ func (cm *CredentialManager) ensureStorageExists() (err error) {
return
}
func
(
cm
*
CredentialManager
)
storeKey
()
error
{
return
ioutil
.
WriteFile
(
cm
.
path
(
skFile
),
cm
.
secretkey
.
Bytes
(),
0600
)
func
(
cm
*
CredentialManager
)
storeSecretKey
(
sk
*
big
.
Int
)
error
{
return
ioutil
.
WriteFile
(
cm
.
path
(
skFile
),
sk
.
Bytes
(),
0600
)
}
// Save the filecontents at the specified path atomically:
// - first save the content in a temp file with a random filename in the same dir
// - then rename the temp file to the specified filepath, overwriting the old file
func
(
cm
*
CredentialManager
)
saveFile
(
filepath
string
,
content
[]
byte
)
(
err
error
)
{
dir
:=
path
.
Dir
(
filepath
)
// Read random data for filename and convert to hex
randBytes
:=
make
([]
byte
,
16
)
_
,
err
=
rand
.
Read
(
randBytes
)
if
err
!=
nil
{
return
}
tempfilename
:=
hex
.
EncodeToString
(
randBytes
)
// Create temp file
err
=
ioutil
.
WriteFile
(
dir
+
"/"
+
tempfilename
,
content
,
0600
)
if
err
!=
nil
{
return
}
// Rename, overwriting old file
return
os
.
Rename
(
dir
+
"/"
+
tempfilename
,
filepath
)
}
func
(
cm
*
CredentialManager
)
storeSignature
(
cred
*
gabi
.
Credential
,
counter
int
)
(
err
error
)
{
...
...
@@ -105,7 +105,7 @@ func (cm *CredentialManager) storeSignature(cred *gabi.Credential, counter int)
}
// TODO existence check
filename
:=
cm
.
path
(
signature
sDir
)
+
"/"
+
cred
.
CredentialType
()
.
Identifier
()
+
"-"
+
strconv
.
Itoa
(
counter
)
filename
:=
cm
.
signature
Filename
(
cred
.
CredentialType
()
.
Identifier
()
,
counter
)
err
=
ioutil
.
WriteFile
(
filename
,
credbytes
,
0600
)
return
}
...
...
@@ -120,3 +120,57 @@ func (cm *CredentialManager) storeAttributes() (err error) {
err
=
ioutil
.
WriteFile
(
cm
.
path
(
attributesFile
),
attrbytes
,
0600
)
return
}
func
(
cm
*
CredentialManager
)
loadSignature
(
id
string
,
counter
int
)
(
signature
*
gabi
.
CLSignature
,
err
error
)
{
path
:=
cm
.
signatureFilename
(
id
,
counter
)
exists
,
err
:=
pathExists
(
path
)
if
err
!=
nil
||
!
exists
{
return
}
bytes
,
err
:=
ioutil
.
ReadFile
(
path
)
signature
=
new
(
gabi
.
CLSignature
)
err
=
json
.
Unmarshal
(
bytes
,
signature
)
return
}
// loadSecretKey retrieves and returns the secret key from storage, or if no secret key
// was found in storage, it generates, saves, and returns a new secret key.
func
(
cm
*
CredentialManager
)
loadSecretKey
()
(
*
big
.
Int
,
error
)
{
exists
,
err
:=
pathExists
(
cm
.
path
(
skFile
))
if
err
!=
nil
{
return
nil
,
err
}
if
exists
{
bytes
,
err
:=
ioutil
.
ReadFile
(
cm
.
path
(
skFile
))
if
err
!=
nil
{
return
nil
,
err
}
return
new
(
big
.
Int
)
.
SetBytes
(
bytes
),
nil
}
else
{
sk
,
err
:=
cm
.
generateSecretKey
()
if
err
!=
nil
{
return
nil
,
err
}
err
=
cm
.
storeSecretKey
(
sk
)
if
err
!=
nil
{
return
nil
,
err
}
return
sk
,
nil
}
}
func
(
cm
*
CredentialManager
)
loadAttributes
()
(
list
map
[
string
][]
*
AttributeList
,
err
error
)
{
list
=
make
(
map
[
string
][]
*
AttributeList
)
exists
,
err
:=
pathExists
(
cm
.
path
(
attributesFile
))
if
err
!=
nil
||
!
exists
{
return
}
bytes
,
err
:=
ioutil
.
ReadFile
(
cm
.
path
(
attributesFile
))
if
err
!=
nil
{
return
nil
,
err
}
return
list
,
json
.
Unmarshal
(
bytes
,
&
list
)
}
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment